2016-01-06 26 views
8

我注意到,有幾個PDF註釋的應用程序(使用Adobe Acrobat,Bluebeam等),有周邊的多邊形創建一個雲計算模式的算法:PDF雲註釋背後的算法是什麼?

Cloud Annotation in PDF

當你拖動這個多邊形的頂點時,雲計算模式重新計算:

Modified Cloud Annotation in PDF

通知弧是如何重新計算環繞多邊形。他們沒有被拉伸或扭曲。無論用什麼算法來定義這個似乎都是一個行業標準。幾個PDF編輯器允許您創建它,並且在每個拖動頂點時,雲弧看起來都是相同的。

我想創建一個複製這個WPF示例應用程序,但我似乎無法找到生成雲模式的任何地方的文檔。

我的圖形設計和2D編程非常流暢,我可以創建拖動頂點的工具,但我需要幫助弄清楚如何繪製這些弧線。它看起來像一系列ArcSegmentsPathGeometry

所以我的問題是,圍繞多邊形創建這些弧的算法是什麼?

我在哪裏可以找到這些行業標準的PDF模式,圖紙,和/或註釋的文檔? (雲,箭頭,邊框等)

回答

12

您草圖中的雲只是沿着每個多邊形邊緣繪製的一系列圓,並具有一定的重疊。

繪製填充的基本雲形狀的簡單方法是首先填充多邊形,然後在填充的多邊形上繪製圓。

當您想用部分透明的顏色填充雲時,該方法會變得平坦,因爲圓與彼此以及與基礎多邊形的重疊將被繪製兩次。它也會錯過雲曲線上的小卡通風格的過沖。

繪製雲的更好方法是首先創建所有圓,然後確定每個圓與其下一個相鄰圓的相交角。然後,您可以創建一個包含圓形段的路徑,您可以填寫該路徑。輪廓由獨立的弧組成,對於終點角度具有較小的偏移量。

在您的示例中,雲弧之間的距離是靜態的。通過使該距離變化並通過強制多邊形邊緣可以被該距離均勻分割,使得多邊形頂點上的弧很容易變化。

下面是JavaScript中的示例實現(沒有多邊形拖動)。我不熟悉C#,但我認爲基本算法很清晰。該代碼是一個完整的網頁,您可以保存並顯示在支持畫布的瀏覽器中;我已經在Firefox中測試過它。

繪製雲的功能需要選項的對象,例如半徑,弧度距離和以度爲單位的過沖。我沒有測試像小多邊形這樣的退化情況,但是在極端情況下,算法應該爲每個多邊形頂點繪製一個弧。

多邊形必須順時針定義。否則,雲將更像雲層中的洞。如果角落附近沒有任何人造物,那將是一個很好的特點。

編輯:我爲下面的雲算法提供了simple online test page。該頁面允許您使用各種參數進行播放。它也很好地顯示了該算法的缺點。 (在FF和Chrome中測試)

當起始角度和結束角度未正確確定時出現人爲現象。以非常鈍的角度,角落旁邊的弧線之間也可能有交點。我還沒有解決這個問題,但我也沒有給出太muczh認爲。)

<!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 = q.x - p.x; 
     var dy = q.y - p.y; 

     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> 
+1

這是迄今爲止我收到的最好的迴應StackOverflow。我希望我能爲您提供更多的積分,讓您獲得難以置信的努力。您的代碼結構合理,易於理解,並且無可否認令人印象深刻!謝謝,姆姆。 – Laith

+0

雖然有一個小問題。如果點(形成一個正方形)從右下,右上,左上和左下開始,則繪製的雲相反。 – chitgoks

+0

@chitgoks:錯誤發生在帖子中,而不是在代碼中。必須定義多邊形的方向,因爲它用於找出什麼是雲的「外部」。這篇文章說的是順時針方向,但是在HTML畫布中,從上到下y是逆時針的。所以交換你的第二和第四個版本,你應該找到。 –

相關問題