How can I fill closed shapes in a row?

I used the html canvas to create this line:

a squiggly line, which loops over itself in some places to create enclosed circle and balloon shapes

I want the loops in the line to fill, so it looks like this:

the enclosed shapes are filled in with red

However, when I fill this out, it just turns into:

the beginning and end of the line have been invisibly connected, and the whole area filled in, enclosed shapes included

I tried to use the paths, it was the same result, only with a line connecting the beginning and the end.

Code abstraction:

var canvas = $("canvas")[0], ctx=canvas.getContext("2d"); ctx.moveto(0,0); // code to stroke path of mouse cursor; 

How can I get the desired result and fill only closed shapes in a row?

+7
javascript canvas
source share
2 answers

Update

The problem is that Brett indicates that fill() implicitly closes the path. We can’t do anything with this using the API, so we need to use a manual approach to fill the loops as separate closed paths.

Intersection search

This algorithm performs the following actions (did not check cases where these loops may overlap, but it should start you). It can also be rewritten to do this in real time by moving the mouse.

  • We can iterate through all segments of the path. Compare each segment with all the others, but from the current + two, since the endpoint of the current segment otherwise will intersect with the starting point of the next segment.
  • If an intersection is found:
    • Draw a path from the intersection, then add points using segments between the first and last intersecting lines

Example

result

 var points = [49,40,49,41,49,42,49,43,49,45,49,48,49,50,49,53,49,56,49,59,49,63,49,67,49,72,50,77,51,82,53,88,53,91,55,96,58,99,60,104,62,106,64,109,65,113,68,116,70,118,72,120,74,121,76,124,78,125,81,126,87,129,92,130,98,133,104,134,109,135,113,135,117,135,121,135,127,135,131,135,135,135,141,132,148,128,153,126,159,122,161,120,164,118,164,116,165,112,165,110,165,107,165,105,165,104,165,101,165,100,164,96,163,94,162,93,160,91,159,90,158,88,157,88,156,88,154,88,151,88,147,88,141,90,135,92,130,94,126,96,121,99,118,101,114,104,111,108,108,110,107,113,104,117,103,120,100,125,99,129,96,135,95,139,95,144,95,148,95,152,95,155,95,158,96,162,97,166,99,170,102,173,106,177,109,181,111,182,113,184,115,185,117,186,119,186,121,186,124,186,127,186,132,185,135,183,141,179,146,175,152,172,158,168,165,165,172,162,178,159,185,158,191,157,195,156,199,156,202,156,206,156,209,156,212,157,216,160,220,163,221,168,224,170,224,173,225,177,227,182,228,186,229,192,229,197,230,203,230,208,230,212,230,219,230,225,230,230,228,236,226,240,221,246,217,251,214,255,210,257,204,260,199,260,194,261,189,261,184,261,181,261,177,261,175,261,173,260,173,256,171,252,170,245,170,237,169,231,168,226,168,221,168,218,168,215,168,212,168,211,169,207,172,205,175,201,180,199,187,198,194,196,201,194,208,194,214,194,221,194,225,194,230,195,235,196,240,199,245,202,247,204,251,207,253,210,254,214,255,216,259,223,263,229,266,235,270,241,273,245,277,253,279,257,283,262,287,269,292,274,297,280,302,285,308,290,314,294,321,295,327,296,336,298,343,298,352,298,359,298,367,292,374,286,379,278,381,269,381,262,381,254,381,246,381,241,379,232,377,229,372,224,369,221,364,219,361,219,355,218,347,218,339,218,330,218,320,221,310,228,300,235,290,242,282,249,276,257,271,263,269,269,267,276,266,281,266,287,266,291,267,297,272,305,279,312,286,319,296,327,305,332,316,338,325,341,333,344,340,348,342,348,344,349,345,349,345,350,346,351,347,353,347,355,347,356,347,358,347,361,347,363,347,366,347,370,347,374,344,379,343,384,342,393,339,400,335,406,331,414,323,421,317,426,310,430,302,435,295,437], ctx = c.getContext("2d"), i, y, ip, t, l = points.length; // compare each segments for(i = 0; i < points.length - 4; i += 2) { for(y = i + 4; y < points.length - 2; y += 2) { ip = intersection(points[i], points[i+1], points[i+2], points[i+3], points[y], points[y+1], points[y+2], points[y+3]); // any intersction? create a sub-path with segments between the intersecting lines if (ip) { ctx.moveTo(ip.x, ip.y); for(t = i + 2; t < y; t += 2) ctx.lineTo(points[t], points[t+1]); } } } // fill all sub-paths at once ctx.fillStyle = "red"; ctx.fill(); // stroke path itself ctx.beginPath(); ctx.moveTo(points[0], points[1]); for(i = 0; i < l; i += 2) ctx.lineTo(points[i], points[i+1]); ctx.stroke(); function intersection(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y) { var d1x = p1x - p0x, d1y = p1y - p0y, d2x = p3x - p2x, d2y = p3y - p2y, d = d1x * d2y - d2x * d1y, px, py, s, t; if (d) { px = p0x - p2x; py = p0y - p2y; s = (d1x * py - d1y * px) / d; if (s >= 0 && s <= 1) { t = (d2x * py - d2y * px) / d; if (t >= 0 && t <= 1) return {x: p0x + (t * d1x), y: p0y + (t * d1y)}; } } return null } 
 <canvas id=c width=500 height=500></canvas> 
+4
source share

The problem is that the fill() method closes the path, basically drawing a line from the start point to the end point. As a result, the entire path is filled, as you saw.

One possible solution, although it will be difficult with an apparently random line, is to return the pointer to a position in which fill() will not cause unwanted fillings. The example below demonstrates this. After drawing the line, I simply return the pointer to the position where closePath() does not cause the filling of the closed areas.

The canvas on the right has an endpoint moved to a neutral position, to fill() behaves as desired.

 var canvas = document.getElementById('myCanvas'); var context = canvas.getContext('2d'); context.beginPath(); context.moveTo(100, 20); context.lineTo(100, 120); context.lineTo(150, 120); context.lineTo(150, 70); context.lineTo(50, 70); context.fill(); context.lineWidth = 2; context.strokeStyle = 'blue'; context.stroke(); var canvas = document.getElementById('myCanvas2'); var context = canvas.getContext('2d'); context.beginPath(); context.moveTo(100, 20); context.lineTo(100, 120); context.lineTo(150, 120); context.lineTo(150, 70); context.lineTo(50, 70); // Go back to a position the line // won't cause unwanted fills context.lineTo(100, 70); context.fill(); context.lineWidth = 2; context.strokeStyle = 'blue'; context.stroke(); 
 <canvas id="myCanvas" width="250" height="250"></canvas> <canvas id="myCanvas2" width="250" height="250"></canvas> 
+4
source share

All Articles