The clouds in your sketches are just a series of circles drawn along each edge of the polygon with a certain overlap.
An easy way to draw a filled main cloud shape would be to first fill the polygon and then draw circles on top of the filled polygon.
This approach crashes when you want to fill the cloud with partially transparent color, because overlapping circles with each other and with the base polygon will be colored twice. He will also miss out on small cartoon-style deviations on crooked clouds.
The best way to attract the cloud is to first create all the circles, and then determine the intersecting angles of each circle with its neighboring neighbor. Then you can create a path with circular segments that you can fill. The contour consists of independent arcs with a slight offset for the final angle.
In your example, the distance between the cloud arcs is static. It is easy to make the arcs at the vertices of the polygons coincide in order to make this distance variable and provided that the edge of the polygon is evenly divided by this distance.
The following is an example JavaScript implementation (without dragging polygons). I am not familiar with C #, but I think the basic algorithm is clear. Code is a complete web page that you can save and display in a browser that supports the canvas; I tested it in Firefox.
The cloud drawing function accepts an object of options such as radius, arc distance, and overshoot in degrees. I have not tested degenerate cases, such as small polygons, but in extreme cases, the algorithm should simply draw one arc for each vertex of the polygon.
The landfill must be determined clockwise. Otherwise, the cloud will be more like a hole in the cloud cover. This would be a nice feature if not for artifacts around the corner arcs.
Change I have provided a simple online test page for the cloud algorithm below. The page allows you to play with various options. It also shows well the flaws of the algorithm. (Tested in FF and Chrome.)
Artifacts occur when the start and end angles are not defined properly. With very obtuse angles, there can also be intersections between arcs near the corner. I did not fix this, but I also did not give it too much thought.)
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Cumulunimbus</title> <script type="text/javascript"> function Point(x, y) { this.x = x; this.y = y; } function get(obj, prop, fallback) { if (obj.hasOwnProperty(prop)) return obj[prop]; return fallback; } /* * Global intersection angles of two circles of the same radius */ function intersect(p, q, r) { var dx = qx - px; var dy = qy - py; var len = Math.sqrt(dx*dx + dy*dy); var a = 0.5 * len / r; if (a < -1) a = -1; if (a > 1) a = 1; var phi = Math.atan2(dy, dx); var gamma = Math.acos(a); return [phi - gamma, Math.PI + phi + gamma]; } /* * Draw a cloud with the given options to the given context */ function cloud(cx, poly, opt) { var radius = get(opt, "radius", 20); var overlap = get(opt, "overlap", 5/6); var stretch = get(opt, "stretch", true); // Create a list of circles var circle = []; var delta = 2 * radius * overlap; var prev = poly[poly.length - 1]; for (var i = 0; i < poly.length; i++) { var curr = poly[i]; var dx = curr.x - prev.x; var dy = curr.y - prev.y; var len = Math.sqrt(dx*dx + dy*dy); dx = dx / len; dy = dy / len; var d = delta; if (stretch) { var n = (len / delta + 0.5) | 0; if (n < 1) n = 1; d = len / n; } for (var a = 0; a + 0.1 * d < len; a += d) { circle.push({ x: prev.x + a * dx, y: prev.y + a * dy, }); } prev = curr; } // Determine intersection angles of circles var prev = circle[circle.length - 1]; for (var i = 0; i < circle.length; i++) { var curr = circle[i]; var angle = intersect(prev, curr, radius); prev.end = angle[0]; curr.begin = angle[1]; prev = curr; } // Draw the cloud cx.save(); if (get(opt, "fill", false)) { cx.fillStyle = opt.fill; cx.beginPath(); for (var i = 0; i < circle.length; i++) { var curr = circle[i]; cx.arc(curr.x, curr.y, radius, curr.begin, curr.end); } cx.fill(); } if (get(opt, "outline", false)) { cx.strokeStyle = opt.outline; cx.lineWidth = get(opt, "width", 1.0); var incise = Math.PI * get(opt, "incise", 15) / 180; for (var i = 0; i < circle.length; i++) { var curr = circle[i]; cx.beginPath(); cx.arc(curr.x, curr.y, radius, curr.begin, curr.end + incise); cx.stroke(); } } cx.restore(); } var poly = [ new Point(250, 50), new Point(450, 150), new Point(350, 450), new Point(50, 300), ]; window.onload = function() { cv = document.getElementById("cv"); cx = cv.getContext("2d"); cloud(cx, poly, { fill: "lightblue", // fill colour outline: "black", // outline colour incise: 15, // overshoot in degrees radius: 20, // arc radius overlap: 0.8333, // arc distance relative to radius stretch: false, // should corner arcs coincide? }); } </script> </head> <body> <canvas width="500" height="500" id="cv"></canvas> </body> </html>