c# - What is the algorithm behind the PDF cloud annotation? -


i've noticed several pdf annotation applications (adobe acrobat, bluebeam, etc) have algorithm creating cloud pattern around polygon:

cloud annotation in pdf

when drag vertices of polygon, cloud pattern recalculated:

modified cloud annotation in pdf

notice how arcs recalculated wrap around polygon. not being stretched or warped. whatever algorithm used define seems industry standard. several pdf editors allow create this, , in each 1 cloud arcs same when dragging vertices.

i trying create wpf sample application replicates this, can't seem find documentation anywhere generating cloud pattern.

i'm quite fluent graphic design , 2d programming, , i'm capable of creating tool drag vertices around, need figuring out how draw these arcs. looks series of arcsegments in pathgeometry.

so question be, what's algorithm create these arcs around polygon?

or

where can find documentation these industry-standard pdf patterns, drawings, and/or annotations? (cloud, arrows, borders, etc)

the clouds in sketches series of circles drawn along each polygon edge overlap.

an easy way draw filled basic cloud shape first fill polygon , draw circles on top of filled polygon.

that approach falls flat when want fill cloud partially transparent colour, because overlap of circles each other , base polygon painted twice. miss small cartoon-style overshoots on cloud curves.

a better way draw cloud create circles first , determine intersecting angles of each circle next neighbour. can create path circle segments, can fill. outline consists of independent arcs small offset end angle.

in example, distance between cloud arcs static. easy make arcs @ polygon vertices coincide making distance variable , enforcing polygon edge evenly divisible distance.

an example implementation in javascript (without polygon dragging) below. i'm not familiar c#, think basic algorithm clear. code complete web page, can save , display in browser supports canvas; i've tested in firefox.

the function draw cloud takes object of options such radius, arc distance , overshoot in degrees. haven't tested degenerate cases small polygons, in extreme case algorithm should draw single arc each polygon vertex.

the polygon must defined clockwise. otherwise, cloud more hole in cloud cover. nice feature, if there weren't artefacts around corner arcs.

edit: i've provided simple online test page cloud algorithm below. page allows play various parameters. shows shortcomings of algorithm nicely. (tested in ff , chrome.)

the artefacts occur when start , end angles not determined properly. obtuse angles, there may intersections between arcs next corner. haven't fixed that, haven't given muczh 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 2 circles of same radius      */     function intersect(p, q, r) {         var dx = q.x - p.x;         var dy = q.y - p.y;          var len = math.sqrt(dx*dx + dy*dy);         var = 0.5 * len / r;          if (a < -1) = -1;         if (a > 1) = 1;          var phi = math.atan2(dy, dx);         var gamma = math.acos(a);          return [phi - gamma, math.pi + phi + gamma];     }      /*      *      draw cloud given options 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 list of circles          var circle = [];                 var delta = 2 * radius * overlap;          var prev = poly[poly.length - 1];         (var = 0; < 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;             }              (var = 0; + 0.1 * d < len; += d) {                 circle.push({                     x: prev.x + * dx,                     y: prev.y + * dy,                 });             }              prev = curr;         }            // determine intersection angles of circles          var prev = circle[circle.length - 1];         (var = 0; < circle.length; i++) {             var curr = circle[i];             var angle = intersect(prev, curr, radius);              prev.end = angle[0];             curr.begin = angle[1];              prev = curr;         }            // draw cloud          cx.save();          if (get(opt, "fill", false)) {             cx.fillstyle = opt.fill;              cx.beginpath();             (var = 0; < 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;              (var = 0; < 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 radius             stretch: false,           // should corner arcs coincide?         });     }  </script> </head>  <body> <canvas width="500" height="500" id="cv"></canvas> </body>  </html> 

Comments

Popular posts from this blog

how to insert data php javascript mysql with multiple array session 2 -

multithreading - Exception in Application constructor -

windows - CertCreateCertificateContext returns CRYPT_E_ASN1_BADTAG / 8009310b -