2016-01-06 8 views
8

मैंने देखा है कई पीडीएफ एनोटेशन अनुप्रयोगों (एडोब एक्रोबैट, Bluebeam, आदि) एक बहुभुज के चारों ओर एक बादल पैटर्न बनाने के लिए एक एल्गोरिथ्म है:पीडीएफ क्लाउड एनोटेशन के पीछे एल्गोरिदम क्या है?

Cloud Annotation in PDF

जब आप इस बहुभुज के कोणबिंदु खींचें, बादल पैटर्न पुनर्गणना की जाती है:

Modified Cloud Annotation in PDF

सूचना कैसे आर्क्स पुनर्गणना कर रहे हैं बहुभुज के आसपास लपेटो करने। वे खिंचाव या warped नहीं जा रहे हैं। जो भी एल्गोरिदम इसे परिभाषित करने के लिए प्रयोग किया जाता है वह एक उद्योग मानक प्रतीत होता है। कई पीडीएफ संपादक आपको इसे बनाने की अनुमति देते हैं, और प्रत्येक में क्लाउड आर्केस को खींचते समय समान दिखता है।

मैं एक WPF नमूना अनुप्रयोग बनाने की कोशिश कर रहा हूं जो इसे दोहराता है, लेकिन मुझे क्लाउड पैटर्न उत्पन्न करने के लिए कहीं भी प्रलेखन नहीं मिल रहा है।

मैं ग्राफिक डिज़ाइन और 2 डी प्रोग्रामिंग के साथ काफी धाराप्रवाह हूं, और मैं चारों ओर कोने को खींचने के लिए टूल बनाने में सक्षम हूं, लेकिन मुझे इन आर्कों को आकर्षित करने के तरीके को जानने में सहायता चाहिए। यह की श्रृंखला PathGeometry में दिखता है।

तो मेरा प्रश्न होगा, बहुभुज के चारों ओर इन आर्कों को बनाने के लिए एल्गोरिदम क्या है?

या

मैं इन उद्योग की मानक पीडीएफ पैटर्न, ड्राइंग, और/या एनोटेशन के लिए दस्तावेज़ कहां मिल सकती है? (बादल, तीर, सीमाएं, आदि)

उत्तर

12

आपके स्केच में बादल प्रत्येक पॉलीगॉन एज के साथ एक निश्चित ओवरलैप के साथ खींची गई मंडलियों की एक श्रृंखला हैं।

भरे मूल क्लाउड आकृति को आकर्षित करने का एक आसान तरीका पहले बहुभुज को भरना होगा और फिर भरे बहुभुज के शीर्ष पर मंडलियों को आकर्षित करना होगा।

जब आप क्लाउड को आंशिक रूप से पारदर्शी रंग से भरना चाहते हैं तो वह दृष्टिकोण फ्लैट गिरता है, क्योंकि एक दूसरे के साथ और बेस पॉलीगॉन के साथ मंडलियों का ओवरलैप दो बार चित्रित किया जाएगा। यह क्लाउड वक्र पर छोटे कार्टून-स्टाइल ओवरहूट को भी याद करेगा।

क्लाउड खींचने का एक बेहतर तरीका पहले सभी मंडलियां बनाना है और उसके बाद अपने अगले पड़ोसी के साथ प्रत्येक सर्कल के अंतरंग कोण निर्धारित करना है। फिर आप सर्कल सेगमेंट के साथ पथ बना सकते हैं, जिसे आप भर सकते हैं। रूपरेखा में अंत कोण के लिए एक छोटे से ऑफसेट के साथ स्वतंत्र चाप शामिल होते हैं।

आपके उदाहरण में, क्लाउड आर्क के बीच की दूरी स्थिर है। पॉलीगॉन शिखर पर आर्क को बनाना आसान है जिससे दूरी दूरी को बनाकर और बहुभुज किनारे को उस दूरी से समान रूप से विभाजित किया जा सके।

जावास्क्रिप्ट में एक उदाहरण कार्यान्वयन (बहुभुज ड्रैगिंग के बिना) नीचे है। मैं सी # से परिचित नहीं हूं, लेकिन मुझे लगता है कि मूल एल्गोरिदम स्पष्ट है। कोड एक पूर्ण वेब पेज है, जिसे आप कैनवास का समर्थन करने वाले ब्राउज़र में सहेज सकते हैं और प्रदर्शित कर सकते हैं; मैंने फ़ायरफ़ॉक्स में इसका परीक्षण किया है।

क्लाउड खींचने के लिए फ़ंक्शन त्रिज्या, आर्क दूरी और डिग्री में ओवरशूट जैसे विकल्पों का एक ऑब्जेक्ट लेता है।मैंने छोटे बहुभुजों जैसे अपमानजनक मामलों का परीक्षण नहीं किया है, लेकिन चरम मामले में एल्गोरिदम को प्रत्येक पॉलीगॉन वर्टेक्स के लिए केवल एक चाप खींचना चाहिए।

बहुभुज को दक्षिणावर्त परिभाषित किया जाना चाहिए। अन्यथा, बादल क्लाउड कवर में एक छेद की तरह होगा। अगर यह कोने आर्क के चारों ओर कोई कलाकृतियों नहीं थे, तो यह एक अच्छी सुविधा होगी।

संपादित करें: मैंने नीचे क्लाउड एल्गोरिदम के लिए simple online test page प्रदान किया है। पृष्ठ आपको विभिन्न मानकों के साथ खेलने की अनुमति देता है। यह अच्छी तरह से एल्गोरिदम की कमियों को भी दिखाता है। (एफएफ और क्रोम में परीक्षण किया गया।)

कलाकृतियों तब होते हैं जब प्रारंभ और अंत कोण ठीक से निर्धारित नहीं होते हैं। बहुत उलझन वाले कोणों के साथ, कोने के बगल में चाप के बीच भी चौराहे हो सकती है। मैं तय कर दी है नहीं है, लेकिन मैं यह भी है कि बहुत 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: त्रुटि पोस्ट में है, कोड में नहीं। बहुभुज का अभिविन्यास परिभाषित किया जाना चाहिए, क्योंकि इसका उपयोग यह पता लगाने के लिए किया जाता है कि क्लाउड के "बाहरी" क्या है। पोस्ट घड़ी की दिशा में कहता है, लेकिन वाईएम के साथ एचटीएमएल कैनवास में ऊपर से नीचे तक, यह anticlockwise है। तो अपनी दूसरी और चौथी verices स्वैप करें, और आपको मिलना चाहिए। –

संबंधित मुद्दे