4

So I've been fiddling with the canvas element, and I seem to have run into a situation that is highly irritating, yet I haven't been able to find a solution. Say that two polygons are drawn on a canvas, and that they should be touching each other. Where one polygon is drawn like this:

ctx.beginPath();
ctx.moveTo(oX,oY);
ctx.lineTo(oX=oX+k,oY=oY-h);
ctx.lineTo(oX=oX+k,oY=oY+h);
ctx.lineTo(oX=oX-k,oY=oY+h);
ctx.lineTo(oX=oX-k,oY=oY-h);
ctx.fill();

A simple version is implemented in this fiddle.

As you can probably see there is a thin line between these shapes. How can I avoid it? I have tried the solutions here, but they don't really seem to refer to this case, because I'm dealing with diagonal lines.

Community
  • 1
  • 1
Kazimieras
  • 607
  • 4
  • 14
  • Possible duplicate of [How can I avoid polygon edge stitching artifacts in HTML5 Canvas?](http://stackoverflow.com/questions/4846911/how-can-i-avoid-polygon-edge-stitching-artifacts-in-html5-canvas) – Jose Gómez May 12 '16 at 22:03

2 Answers2

4

One solution

You could always use the stroke-line trick, but depending on your goal:

If it is to show many polygons next to each other, you could look at the polygons as simple squares.

  • Draw them in as such in an off-screen canvas next to each other. This will produce a result with no gaps.
  • Then transform the main canvas into the position you want those polygons to appear. Add rotation and/or skew depending on goal.
  • Finally, draw the off-screen canvas onto the main canvas as an image. Problem gone.

This will give you an accurate result with no extra steps in stroking, and the calculations for the boxes becomes very simple and fast to do (think 2d grid).

You have to use an off-screen canvas though. If you transform main canvas and draw in the shapes you will encounter the same problem as already present. This is because each point is transformed and if there is need for interpolation it will be calculated for each path shape separately. Drawing in an image will add interpolation on the whole surface, and only where there are gaps (non-opaque alpha). As we already are "gap-free" this is no longer a problem.

This will require an extra step in planning to place them correctly, but this is a simple step.

Example

Step 1 - draw boxes into an off-screen canvas:

This code draws on the off-screen canvas resulting in two boxes with no gap:

snap

(the example uses an on-screen to show result, see next step for usage of off-screen canvas)

var ctx = document.querySelector("canvas").getContext("2d");
ctx.fillStyle = "red";

ctx.fillRect(10, 10, 50, 50);
ctx.fillRect(60, 10, 50, 50);
<canvas/>

Step 2 - transform main canvas and draw in off-screen canvas

When drawn into main canvas with transformation set, the result will be (pseudo-random transformation just to show):

snap2

var ctx = document.querySelector("canvas").getContext("2d");

// off-screen canvas
var octx = document.createElement("canvas").getContext("2d");
octx.fillStyle = "red";
octx.fillRect(10, 10, 50, 50);
octx.fillRect(60, 10, 50, 50);

// transform and draw to main
ctx.translate(80, 0);
ctx.rotate(0.5, Math.PI);
ctx.transform(1, 0, Math.tan(-0.5),1, 0,0); // skew
ctx.drawImage(octx.canvas, 0, 0);
<canvas />

Step 3 (optional) - Interaction

If you want to interact with the boxes you simply apply the same transform, then add path for a box and hit-test it against the mouse position. Redraw a single state, erase by clearing and draw back the off-screen canvas on top:

var ctx = document.querySelector("canvas").getContext("2d");

// off-screen canvas
var octx = document.createElement("canvas").getContext("2d");
octx.fillStyle = "red";
octx.fillRect(10, 10, 50, 50);
octx.fillRect(60, 10, 50, 50);

// allow us to reuse some of the steps:
function getTransforms() {
  ctx.setTransform(1,0,0,1,0,0);
  ctx.translate(80, 0);
  ctx.rotate(0.5, Math.PI);
  ctx.transform(1, 0, Math.tan(-0.5),1, 0,0); // skew
}

function clear() {
  ctx.setTransform(1,0,0,1,0,0);
  ctx.clearRect(0,0,300,150);
}

function redraw() {
  ctx.drawImage(octx.canvas, 0, 0);
}

getTransforms();
redraw();

ctx.canvas.onmousemove = function(e) {
  var r = this.getBoundingClientRect(),
      x = e.clientX - r.left, y = e.clientY - r.top;
  
  // box 1 (for many, use array)
  ctx.beginPath();
  ctx.rect(10, 10, 50, 50);

  clear();         // these can be optimized to use state-flags
  getTransforms(); //  so they aren't redraw for every move...
  redraw();

  // just one box check here
  if (ctx.isPointInPath(x, y)) {
      ctx.fill();
  }
  
};
<canvas />
Community
  • 1
  • 1
1

Yes, it's annoying when filled polygons result in that tiny gap. It's especially common on diagonals that should theoretically meet.

A common workaround is to put a half-pixel, same-colored stroke around the polygons:

//Some basic setup ...
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var oX = 50;
var oY = 50;
var h = 33;
var k = 50;
ctx.fillStyle = 'red';
ctx.strokeStyle='red';
ctx.lineWidth=0.50;

//Draw one polygon
ctx.beginPath();
ctx.moveTo(oX,oY);
ctx.lineTo(oX=oX+k,oY=oY-h);
ctx.lineTo(oX=oX+k,oY=oY+h);
ctx.lineTo(oX=oX-k,oY=oY+h);
ctx.lineTo(oX=oX-k,oY=oY-h);
ctx.fill();
ctx.stroke();
//Draw another polygon
oX = oX+k;
oY = oY+h;
ctx.beginPath();
ctx.moveTo(oX,oY);
ctx.lineTo(oX=oX+k,oY=oY-h);
ctx.lineTo(oX=oX+k,oY=oY+h);
ctx.lineTo(oX=oX-k,oY=oY+h);
ctx.lineTo(oX=oX-k,oY=oY-h);
ctx.fill();
ctx.stroke();

var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");



//Some basic setup ...
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var oX = 50;
var oY = 50;
var h = 33;
var k = 50;
ctx.fillStyle = 'red';
ctx.strokeStyle='red';
ctx.lineWidth=0.50;

//Draw one polygon
ctx.beginPath();
ctx.moveTo(oX,oY);
ctx.lineTo(oX=oX+k,oY=oY-h);
ctx.lineTo(oX=oX+k,oY=oY+h);
ctx.lineTo(oX=oX-k,oY=oY+h);
ctx.lineTo(oX=oX-k,oY=oY-h);
ctx.fill();
ctx.stroke();
//Draw another polygon
oX = oX+k;
oY = oY+h;
ctx.beginPath();
ctx.moveTo(oX,oY);
ctx.lineTo(oX=oX+k,oY=oY-h);
ctx.lineTo(oX=oX+k,oY=oY+h);
ctx.lineTo(oX=oX-k,oY=oY+h);
ctx.lineTo(oX=oX-k,oY=oY-h);
ctx.fill();
ctx.stroke();
#canvas{border:1px solid red;}
<canvas id="canvas" width=300 height=300></canvas>
markE
  • 102,905
  • 11
  • 164
  • 176
  • 2
    That workaround is fine, unless the polygons have transparency, in which case it makes the problem much worse. – Jose Gómez May 12 '16 at 21:58