对不起,我的数学知识贫乏。
我怎样才能画这样的平行线:
这是我当前的代码:
<canvas id='canvas' resize></canvas>
我正在使用 PaperJS ( http://paperjs.org ) :
<script type='text/javascript' src='http://paperjs.org/assets/js/paper.js'></script>
这是我的脚本:
<script type='text/paperscript' canvas='canvas'>
var path1 = new Path();
var path2 = new Path();
var path3 = new Path();
var distance = 20;
path1.strokeWidth = 2.0;
path1.strokeColor = 'black';
path2.strokeWidth = 2.0;
path2.strokeColor = 'black';
path2.dashArray = [4, 4];
path3.strokeWidth = 2.0;
path3.strokeColor = 'black';
function onMouseDown (event) {
path2.add(event.point);
path1.add(event.point - distance);
path3.add(event.point + distance);
};
</script>
这是我的糟糕结果(我用红色圆圈四舍五入):
最佳答案
您需要创建指向源路径的挤压和斜切路径,这让我想起了 Hans Muller 的这篇文章。
归属说明:
Hans Muller 写了几篇关于在 Webkit 和 Blink 中提供 CSS shape-margin
和 shape-padding
所做工作的博客文章。
http://hansmuller-webkit.blogspot.com/2014/03/a-simpler-algorithm-for-css-shapes.html
http://hansmuller-webkit.blogspot.com/2013/04/growing-and-shrinking-polygons-round-one.html
计算形状外部的 CSS 边距路径和形状内部的 CSS 填充路径的相同代码可用于创建平行路径。
这是帖子中的演示,显示了给定路径内外的“平行”路径:
var shapeMargin = 10;
var shapePadding = 10;
var polygon;
var marginPolygon;
var paddingPolygon;
var dragVertexIndex = null;
var hoverLocation = null;
var polygonVertexRadius = 9;
function getCanvas() { return document.getElementById("demo-canvas"); }
function drawPolygonVertexLabels(g, p)
{
for (var i = 0; i < p.vertices.length; i++) {
var vertex = p.vertices[i];
if (vertex.hidden)
continue;
g.fillText(vertex.label, vertex.x - 3, vertex.y + 4);
}
}
function drawPolygonVertices(g, p, r)
{
g.strokeStyle = "none";
for (var i = 0; i < p.vertices.length; i++) {
var vertex = p.vertices[i];
if (vertex.hidden)
return;
g.beginPath();
g.arc(vertex.x, vertex.y, r, 0, Math.PI*2, false)
g.fill();
/*
if (vertex.isReflex) {
g.strokeStyle = "rgb(238,236,230)";
g.lineWidth = 1;
g.arc(vertex.x, vertex.y, polygonVertexRadius+2, 0, Math.PI*2, false);
g.stroke();
}
*/
g.closePath();
}
}
function drawPolygonEdges(g, p)
{
if (p.vertices.length == 0)
return;
g.beginPath();
for (var i = 0; i < p.vertices.length; i++) {
var vertex = p.vertices[i];
if (i == 0)
g.moveTo(vertex.x, vertex.y);
else
g.lineTo(vertex.x, vertex.y);
}
if (polygon.closed)
g.lineTo(p.vertices[0].x, p.vertices[0].y);
g.stroke();
g.closePath();
}
function drawPolygonOffsetEdges(g, p)
{
var edges = p.offsetEdges;
if (!edges || edges.length == 0)
return;
g.beginPath();
for (var i = 0; i < edges.length; i++) {
var edge = edges[i];
g.moveTo(edge.vertex1.x, edge.vertex1.y);
g.lineTo(edge.vertex2.x, edge.vertex2.y);
}
g.stroke();
g.closePath();
}
function draw() {
var canvas = getCanvas();
var g = canvas.getContext("2d");
g.clearRect(0, 0, canvas.width, canvas.height);
// marginPolygon
g.fillStyle = "none";
g.strokeStyle = "rgba(238,236,230,0.5)";
g.lineWidth = "1";
drawPolygonOffsetEdges(g, marginPolygon);
g.strokeStyle = "rgb(79,129,189)";
g.lineWidth = "2";
g.fillStyle = "none";
drawPolygonEdges(g, marginPolygon);
g.fillStyle = "rgb(79,129,189)";
drawPolygonVertices(g, marginPolygon, polygonVertexRadius - 4);
// paddingPolygon
g.strokeStyle = "rgba(238,236,230,0.5)"
g.lineWidth = "1";
drawPolygonOffsetEdges(g, paddingPolygon);
g.strokeStyle = "rgb(119,146,60)";
g.lineWidth = "2";
g.fillStyle = "none";
drawPolygonEdges(g, paddingPolygon);
g.fillStyle = "rgb(119,146,60)";
drawPolygonVertices(g, paddingPolygon, polygonVertexRadius - 4);
// polygon
g.strokeStyle = "rgb(238,236,230)";
g.fillStyle = "none";
g.lineWidth = "1";
drawPolygonEdges(g, polygon);
g.fillStyle = "rgb(255,161,0)";
drawPolygonVertices(g, polygon, polygonVertexRadius);
g.font = "12px Arial";
g.fillStyle = "black";
drawPolygonVertexLabels(g, polygon);
}
// See http://paulbourke.net/geometry/pointlineplane/
function distanceToEdgeSquared(p1, p2, p3)
{
var dx = p2.x - p1.x;
var dy = p2.y - p1.y;
if (dx == 0 || dy == 0)
return Number.POSITIVE_INFNITY;
var u = ((p3.x - p1.x) * dx + (p3.y - p1.y) * dy) / (dx * dx + dy * dy);
if (u < 0 || u > 1)
return Number.POSITIVE_INFINITY;
var x = p1.x + u * dx; // closest point on edge p1,p2 to p3
var y = p1.y + u * dy;
return Math.pow(p3.x - x, 2) + Math.pow(p3.y - y, 2);
}
function polygonVertexNear(p)
{
var thresholdDistanceSquared = polygonVertexRadius * polygonVertexRadius * 2;
for (var i = 0; i < polygon.vertices.length; i++) {
var vertex = polygon.vertices[i];
var dx = vertex.x - p.x;
var dy = vertex.y - p.y;
if (dx*dx + dy*dy < thresholdDistanceSquared)
return i;
}
return null;
}
function polygonEdgeNear(p)
{
var thresholdDistanceSquared = polygonVertexRadius * polygonVertexRadius * 2;
for (var i = 0; i < polygon.vertices.length; i++) {
var v0 = polygon.vertices[i];
var v1 = polygon.vertices[(i + 1) % polygon.vertices.length];
if (distanceToEdgeSquared(v0, v1, p) < thresholdDistanceSquared)
return {index0: i, index1: (i + 1) % polygon.vertices.length};
}
return null;
}
// See http://hansmuller-webkit.blogspot.com/2013/02/where-is-mouse.html
function canvasEventLocation(event)
{
var canvas = getCanvas();
var style = document.defaultView.getComputedStyle(canvas, null);
function styleValue(property) {
return parseInt(style.getPropertyValue(property), 10) || 0;
}
var scaleX = canvas.width / styleValue("width");
var scaleY = canvas.height / styleValue("height");
var canvasRect = canvas.getBoundingClientRect();
var canvasX = scaleX * (event.clientX - canvasRect.left - canvas.clientLeft - styleValue("padding-left"));
var canvasY = scaleY * (event.clientY - canvasRect.top - canvas.clientTop - styleValue("padding-top"))
return {x: canvasX, y: canvasY};
}
function handleMouseDown(event)
{
var eventXY = canvasEventLocation(event);
getCanvas().addEventListener("mousemove", handleMouseMove, false);
if (polygon.closed) {
dragVertexIndex = polygonVertexNear(eventXY);
if (dragVertexIndex == null) {
var edge = polygonEdgeNear(canvasEventLocation(event));
if (edge != null) {
polygon.vertices.splice(edge.index1, 0, eventXY);
computeAll();
}
}
}
else
{
polygon.closed = polygonVertexNear(eventXY) != null;
if (!polygon.closed)
polygon.vertices.push(eventXY);
else
computeAll();
}
// The following appears to be the only way to prevent Chrome from showing the text select cursor.
// For the record: hacks based on -webkit-user-select: none, or #canvas:focus,#canvas:active do not
// currently work.
event.preventDefault();
event.stopPropagation();
draw();
}
function handleMouseMove(event)
{
if (dragVertexIndex != null) {
var eventXY = canvasEventLocation(event);
polygon.vertices[dragVertexIndex].x = eventXY.x;
polygon.vertices[dragVertexIndex].y = eventXY.y;
computeAll();
draw();
}
}
function handleMouseUp(event)
{
getCanvas().removeEventListener("mousemove", handleMouseMove);
dragVertexIndex = null;
draw();
}
function handleSliderChange()
{
function $(id) { return document.getElementById(id); }
shapeMargin = parseInt($("slider.shapeMargin").value);
$("value.shapeMargin").innerHTML = shapeMargin;
shapePadding = parseInt($("slider.shapePadding").value);
$("value.shapePadding").innerHTML = shapePadding;
computeAll();
draw();
}
function inwardEdgeNormal(edge)
{
// Assuming that polygon vertices are in clockwise order
var dx = edge.vertex2.x - edge.vertex1.x;
var dy = edge.vertex2.y - edge.vertex1.y;
var edgeLength = Math.sqrt(dx*dx + dy*dy);
return {x: -dy/edgeLength, y: dx/edgeLength};
}
function outwardEdgeNormal(edge)
{
var n = inwardEdgeNormal(edge);
return {x: -n.x, y: -n.y};
}
// If the slope of line vertex1,vertex2 greater than the slope of vertex1,p then p is on the left side of vertex1,vertex2 and the return value is > 0.
// If p is colinear with vertex1,vertex2 then return 0, otherwise return a value < 0.
function leftSide(vertex1, vertex2, p)
{
return ((p.x - vertex1.x) * (vertex2.y - vertex1.y)) - ((vertex2.x - vertex1.x) * (p.y - vertex1.y));
}
function isReflexVertex(polygon, vertexIndex)
{
// Assuming that polygon vertices are in clockwise order
var thisVertex = polygon.vertices[vertexIndex];
var nextVertex = polygon.vertices[(vertexIndex + 1) % polygon.vertices.length];
var prevVertex = polygon.vertices[(vertexIndex + polygon.vertices.length - 1) % polygon.vertices.length];
if (leftSide(prevVertex, nextVertex, thisVertex) < 0)
return true; // TBD: return true if thisVertex is inside polygon when thisVertex isn't included
return false;
}
function createPolygon(vertices)
{
var polygon = {vertices: vertices};
var edges = [];
var minX = (vertices.length > 0) ? vertices[0].x : undefined;
var minY = (vertices.length > 0) ? vertices[0].y : undefined;
var maxX = minX;
var maxY = minY;
for (var i = 0; i < polygon.vertices.length; i++) {
vertices[i].label = String(i);
vertices[i].isReflex = isReflexVertex(polygon, i);
var edge = {
vertex1: vertices[i],
vertex2: vertices[(i + 1) % vertices.length],
polygon: polygon,
index: i
};
edge.outwardNormal = outwardEdgeNormal(edge);
edge.inwardNormal = inwardEdgeNormal(edge);
edges.push(edge);
var x = vertices[i].x;
var y = vertices[i].y;
minX = Math.min(x, minX);
minY = Math.min(y, minY);
maxX = Math.max(x, maxX);
maxY = Math.max(y, maxY);
}
polygon.edges = edges;
polygon.minX = minX;
polygon.minY = minY;
polygon.maxX = maxX;
polygon.maxY = maxY;
polygon.closed = true;
return polygon;
}
// based on http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline2d/, edgeA => "line a", edgeB => "line b"
function edgesIntersection(edgeA, edgeB)
{
var den = (edgeB.vertex2.y - edgeB.vertex1.y) * (edgeA.vertex2.x - edgeA.vertex1.x) - (edgeB.vertex2.x - edgeB.vertex1.x) * (edgeA.vertex2.y - edgeA.vertex1.y);
if (den == 0)
return null; // lines are parallel or conincident
var ua = ((edgeB.vertex2.x - edgeB.vertex1.x) * (edgeA.vertex1.y - edgeB.vertex1.y) - (edgeB.vertex2.y - edgeB.vertex1.y) * (edgeA.vertex1.x - edgeB.vertex1.x)) / den;
var ub = ((edgeA.vertex2.x - edgeA.vertex1.x) * (edgeA.vertex1.y - edgeB.vertex1.y) - (edgeA.vertex2.y - edgeA.vertex1.y) * (edgeA.vertex1.x - edgeB.vertex1.x)) / den;
if (ua < 0 || ub < 0 || ua > 1 || ub > 1)
return null;
return {x: edgeA.vertex1.x + ua * (edgeA.vertex2.x - edgeA.vertex1.x), y: edgeA.vertex1.y + ua * (edgeA.vertex2.y - edgeA.vertex1.y)};
}
function appendArc(vertices, center, radius, startVertex, endVertex, isPaddingBoundary)
{
const twoPI = Math.PI * 2;
var startAngle = Math.atan2(startVertex.y - center.y, startVertex.x - center.x);
var endAngle = Math.atan2(endVertex.y - center.y, endVertex.x - center.x);
if (startAngle < 0)
startAngle += twoPI;
if (endAngle < 0)
endAngle += twoPI;
var arcSegmentCount = 5; // An odd number so that one arc vertex will be eactly arcRadius from center.
var angle = ((startAngle > endAngle) ? (startAngle - endAngle) : (startAngle + twoPI - endAngle));
var angle5 = ((isPaddingBoundary) ? -angle : twoPI - angle) / arcSegmentCount;
vertices.push(startVertex);
for (var i = 1; i < arcSegmentCount; ++i) {
var angle = startAngle + angle5 * i;
var vertex = {
x: center.x + Math.cos(angle) * radius,
y: center.y + Math.sin(angle) * radius,
};
vertices.push(vertex);
}
vertices.push(endVertex);
}
function createOffsetEdge(edge, dx, dy)
{
return {
vertex1: {x: edge.vertex1.x + dx, y: edge.vertex1.y + dy},
vertex2: {x: edge.vertex2.x + dx, y: edge.vertex2.y + dy}
};
}
function createMarginPolygon(polygon)
{
var offsetEdges = [];
for (var i = 0; i < polygon.edges.length; i++) {
var edge = polygon.edges[i];
var dx = edge.outwardNormal.x * shapeMargin;
var dy = edge.outwardNormal.y * shapeMargin;
offsetEdges.push(createOffsetEdge(edge, dx, dy));
}
var vertices = [];
for (var i = 0; i < offsetEdges.length; i++) {
var thisEdge = offsetEdges[i];
var prevEdge = offsetEdges[(i + offsetEdges.length - 1) % offsetEdges.length];
var vertex = edgesIntersection(prevEdge, thisEdge);
if (vertex)
vertices.push(vertex);
else {
var arcCenter = polygon.edges[i].vertex1;
appendArc(vertices, arcCenter, shapeMargin, prevEdge.vertex2, thisEdge.vertex1, false);
}
}
var marginPolygon = createPolygon(vertices);
marginPolygon.offsetEdges = offsetEdges;
return marginPolygon;
}
function createPaddingPolygon(polygon)
{
var offsetEdges = [];
for (var i = 0; i < polygon.edges.length; i++) {
var edge = polygon.edges[i];
var dx = edge.inwardNormal.x * shapePadding;
var dy = edge.inwardNormal.y * shapePadding;
offsetEdges.push(createOffsetEdge(edge, dx, dy));
}
var vertices = [];
for (var i = 0; i < offsetEdges.length; i++) {
var thisEdge = offsetEdges[i];
var prevEdge = offsetEdges[(i + offsetEdges.length - 1) % offsetEdges.length];
var vertex = edgesIntersection(prevEdge, thisEdge);
if (vertex)
vertices.push(vertex);
else {
var arcCenter = polygon.edges[i].vertex1;
appendArc(vertices, arcCenter, shapePadding, prevEdge.vertex2, thisEdge.vertex1, true);
}
}
var paddingPolygon = createPolygon(vertices);
paddingPolygon.offsetEdges = offsetEdges;
return paddingPolygon;
}
function computeAll()
{
polygon = createPolygon(polygon.vertices);
marginPolygon = createMarginPolygon(polygon);
paddingPolygon = createPaddingPolygon(polygon);
}
function init()
{
var polygonVertices = [{x: 143, y: 327}, {x: 80, y: 236}, {x: 151, y: 148}, {x: 454, y: 69}, {x: 560, y: 320}];
polygon = createPolygon(polygonVertices);
var canvas = getCanvas();
canvas.addEventListener("mousedown", handleMouseDown, false);
canvas.addEventListener("mouseup", handleMouseUp, false);
var sliderNames = ["slider.shapeMargin", "slider.shapePadding"];
for (var i = 0; i < sliderNames.length; i++) {
var slider = document.getElementById(sliderNames[i]);
slider.onchange = handleSliderChange;
}
computeAll();
draw();
}
init();
#demo-canvas {
border: solid black 4px;
margin: 10px;
cursor: default;
background-color: #636363;
}
.gui {
display: table;
}
.gui-row {
display: table-row;
}
.gui-label {
display: table-cell;
text-align: end;
margin: 1em;
width: 200px;
}
.gui-input {
display: table-cell;
margin: 1em;
}
.gui-value {
display: table-cell;
margin: 1em;
}
<h4>Drag the numbered path vertices and the parallel lines adjust.</h4>
<canvas id="demo-canvas" width="650" height="400"></canvas>
<div class="gui">
<div class="gui-row">
<label class="gui-label" for="slider.shapeMargin">Shape Margin</label>
<input class="gui-input" id="slider.shapeMargin" value="10" min="0" max="50" type="range" />
<label class="gui-value" id="value.shapeMargin">10</label>
</div>
<div class="gui-row">
<label class="gui-label" for="slider.shapePadding">Shape Padding</label>
<input class="gui-input" id="slider.shapePadding" value="10" min="0" max="50" type="range" />
<label class="gui-value" id="value.shapePadding">10</label>
</div>
</div>
关于javascript - 如何用 Canvas PaperJS 绘制平行线? ( Canvas /Javascript),我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/33737862/