How to draw on canvas more accurately to avoid background leakage from the bottom

I wanted to write a small library that would draw all kinds of patterns on canvas. I am faced with the problem that when I draw two triangles next to each other, they do not stick to each other and leave a small gap.

You can see it all there https://jsfiddle.net/dufia/8ggwn9mq/1/

function drawEquilateralTriangleB(ctx, startingPoint, size, color, opacity, row, column) { var h = size * Math.cos(Math.PI/6); ctx.beginPath(); ctx.moveTo(startingPoint.x - (size / 2) + 0.5, startingPoint.y + 0.5); ctx.lineTo(startingPoint.x - (size / 2) + (size / 2) + 0.5, startingPoint.y + h + 0.5); ctx.lineTo(startingPoint.x + (size / 2) + 0.5, startingPoint.y + 0.5); ctx.lineTo(startingPoint.x + (size / 2) + 0.5, startingPoint.y + 0.5); ctx.fillStyle = color; ctx.globalAlpha = opacity; ctx.fill(); }; 

Between the triangles you can see the white lines, which are actually the background, the problem does not affect the squares.

I tried using 0.5 coordinates and messed up with the punches. Strokes can do the job when the opacity is 100%, but they all look corrupted when the template is transparent.

Is there a solid fix?

+1
source share
1 answer

This is very annoying. The same thing happens in SVG. It can destroy the image after you have spent so much effort to make everything line up.

Even rounding pixels to the borders of pixels or centers did not help. What I found helped, in the end, add a 0.5-pixel stroke to the fill and compensate for the coordinated 0.5 pixels. Math doesn't make sense, but it all matters.

Below is an example of the problem and the solution I used. Move the mouse over the image to view a larger image and see various artifacts.

One problem remains - the darkening of the joining pixels. This is due to (ALL) browsers using the wrong color model for color mixing. They average the channel with

 colorCh = (colorC1 + colorC2) / 2; // incorrect 

The color channel values ​​are the square root of the output of the display device. The formula used darkens all mixes. (I really want them to fix it). You can see it in the example. The bottom two have adjacent pixels that are too dark.

Right mix

 colorCh = sqrt( (pow(colorC1, 2 ) + pow(colorC2, 2)) / 2); // correct 

And fix this last small (big problem) problem.

 // get a canvas var canvas = document.getElementById("canV"); var ctx = canvas.getContext("2d"); // mouse stuff var mouse = {x:0,y:0} // mouse pos on canvas function mouseMove(event){ // mouse event listener mouse.x = event.offsetX; mouse.y = event.offsetY; if(mouse.x === undefined){ mouse.x = event.clientX; mouse.y = event.clientY;} } // add the mouse listener canvas.addEventListener('mousemove',mouseMove); // clear the background with white ctx.fillStyle = "white"; ctx.fillRect(0,0,canvas.width/2,canvas.height); // thing to draw // 4 tri polys with four colours var points = [-50,-50,50,-50,50,50,-50,50,0,0] var polys = [[0,1,4],[1,2,4],[2,3,4],[3,0,4]]; var cols = ["red","green","blue","purple"]; // draws the polys normaly at the location x,y function drawPolysAt(x,y){ for(var i = 0; i < polys.length; i++){ var p = polys[i]; ctx.fillStyle = cols[i]; ctx.beginPath(); ctx.moveTo(points[p[0]*2]+x,points[p[0]*2+1]+y) for(var j = 1; j< p.length; j++){ ctx.lineTo(points[p[j]*2]+x,points[p[j]*2+1]+y); } ctx.fill(); } } // draws the polys with outlining stroke 0.5 pixels // wide. function drawPolysAtFix(x,y){ ctx.lineWith = 0.5; for(var i = 0; i < polys.length; i++){ var p = polys[i]; ctx.fillStyle = cols[i]; ctx.strokeStyle = cols[i]; ctx.beginPath(); ctx.moveTo(points[p[0]*2]+x,points[p[0]*2+1]+y) for(var j = 1; j< p.length; j++){ ctx.lineTo(points[p[j]*2]+x,points[p[j]*2+1]+y); } ctx.stroke(); ctx.fill(); } } // draws the help text ctx.font = "12px verdana"; function text(text,x,y,col){ ctx.textAlign = "center"; ctx.textBaseline = "top"; ctx.fillStyle = col; ctx.fillText(text,x,y); } // draw the first example var posX = 60; var posY = 60; drawPolysAt(posX,posY); text("Drawn on pixel",posX,posY+52,"black"); text("boundaries.",posX,posY+52+14,"black"); // draw the second example offest by 0.5 pixels posX += 120; drawPolysAt(posX+0.5,posY+0.5); text("Drawn offset by",posX,posY+52,"black"); text("0.5 pixels",posX,posY +52+14,"black"); posX -= 60; text("Makes no differance",posX,posY+55+12+16,"black"); text("Appart from one pixel bleed ",posX,posY+55+12*2+16,"black"); text("on right due to offset ",posX,posY+55+12*3+16,"black"); // draw the thrid example with the stroke but not offset posX = 60; posY = 240; drawPolysAtFix(posX,posY); text("With 0.5 pixel",posX,posY+52,"black"); text("stroke. But Bleeds!",posX,posY+52+14,"black"); // draw with stroke and 0.5 pixel offset posX += 120 drawPolysAtFix(posX+0.5,posY+0.5); text("With 0.5 Stroke",posX,posY+52,"black"); text("and offset 0.5 pixels",posX,posY +52+14,"black"); posX -= 60; text("Right side is best solution ",posX,posY+55+14+16,"black"); function update(){ // to keep the zoom crisp ctx.imageSmoothingEnabled = false; // zoom around mouse on right side ctx.drawImage(canvas,mouse.x-20,mouse.y-20,40,40,canvas.width/2,0,canvas.width/2,canvas.height) requestAnimationFrame(update) } update(); 
 .canC { width:500px; height:400px;} .info { font-size:x-small; } 
 <div class="info"> Top left drawn normaly at pixel boundaries, with white pixels showing through joins.<br> Top right drawn normaly at pixel centers (offset 0.5,0.5 pixels) bleeds into sourounding pixels.<br> Bottom left drawn with 0.5 pixel stroke at pixel boundaries. Creates a blured (bleeding) edge<br> Bottom right. Drawn with 0.5 pixel stroke and offset by 0.5 pixels. Note that there is no bleeding around the outside and the joins are the best posible. <canvas class="canC" id="canV" width=500 height=400></canvas> 
+3
source

Source: https://habr.com/ru/post/1238524/


All Articles