いくつかのPDF注釈アプリケーション(Adobe Acrobat、Bluebeamなど)には、ポリゴンの周りに雲のパターンを作成するためのアルゴリズムがあることに気づきました。
このポリゴンの頂点をドラッグすると、雲のパターンが再計算されます。
円弧がポリゴンをラップするように再計算される方法に注目してください。それらは引き伸ばされたり歪んだりしていません。これを定義するために使用されるアルゴリズムは、業界標準のようです。いくつかのPDFエディターを使用すると、これを作成できます。それぞれのエディターで、頂点をドラッグしたときに雲の弧が同じように見えます。
これを複製するWPFサンプルアプリケーションを作成しようとしていますが、クラウドパターンを生成するためのドキュメントがどこにも見つからないようです。
I'm quite fluent with graphic design and 2D programming, and I'm capable of creating the tool to drag the vertices around, but I need help with figuring out how to draw these arcs. It looks like a series of ArcSegments
in a PathGeometry
.
So my question would be, what's the algorithm to create these arcs around a polygon?
or
Where can I find the documentation for these industry-standard PDF patterns, drawings, and/or annotations? (Cloud, arrows, borders, etc)
The clouds in your sketches are just a series of circles drawn along each polygon edge with a certain overlap.
An easy way to draw the filled basic cloud shape would be to first fill the polygon and then draw the circles on top of the filled polygon.
That approach falls flat when you want to fill the cloud with a partially transparent colour, because the overlap of the circles with each other and with the base polygon will be painted twice. It will also miss the small cartoon-style overshoots on the cloud curves.
A better way to draw the cloud is to create all circles first and then determine the intersecting angles of each circle with its next neighbour. You can then create a path with circle segments, which you can fill. The outline consists of independent arcs with a small offset for the end angle.
In your example, the distance between the cloud arcs is static. It is easy to make the arcs at polygon vertices coincide by making that distance variable and by enforcing that the polygon edge is evenly divisible by that distance.
An example implementation in JavaScript (without the polygon dragging) is below. I'm not familiar with C#, but I think the basic algorithm is clear. The code is a complete web page, that you can save and display in browser that supports the canvas; I've tested it in Firefox.
The function to draw the cloud takes an object of options such as the radius, the arc distance and the overshoot in degrees. I haven't tested degenerate cases like small polygons, but in the extreme case the algorithm should just draw a single arc for each polygon vertex.
The polygon must be defined clockwise. Otherwise, the cloud will be more like a hole in cloud cover. That would be a nice feature, if there weren't any artefacts around the corner arcs.
編集:以下にクラウドアルゴリズムの簡単なオンラインテストページを提供しました。このページでは、さまざまなパラメーターを試すことができます。また、アルゴリズムの欠点もうまく示しています。(FFとChromeでテスト済み。)
アーティファクトは、開始角度と終了角度が適切に決定されていない場合に発生します。非常に鈍角の場合、コーナーの隣の円弧の間に交差がある場合もあります。私はそれを修正していませんが、私もそれほど多くのことを考えていません。)
<!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>
この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。
侵害の場合は、連絡してください[email protected]
コメントを追加