如何使用javascript HTML5画布通过N个点绘制平滑曲线?
Posted
技术标签:
【中文标题】如何使用javascript HTML5画布通过N个点绘制平滑曲线?【英文标题】:how to draw smooth curve through N points using javascript HTML5 canvas? 【发布时间】:2011-10-26 14:59:55 【问题描述】:对于绘图应用程序,我将鼠标移动坐标保存到一个数组中,然后使用 lineTo 绘制它们。结果线不平滑。如何在所有收集的点之间生成一条曲线?
我搜索过,但只找到了 3 个用于绘制线条的函数:对于 2 个样本点,只需使用 lineTo
。对于 3 个采样点 quadraticCurveTo
,对于 4 个采样点,bezierCurveTo
。
(我尝试为数组中的每 4 个点绘制一个 bezierCurveTo
,但这会导致每 4 个样本点出现扭结,而不是连续平滑曲线。)
如何编写一个函数来绘制具有 5 个及以上样本点的平滑曲线?
【问题讨论】:
“顺利”是什么意思?无限微分?两次可微?三次样条(“Bezier 曲线”)具有许多良好的属性,并且可以二次微分,并且很容易计算。 @Kerrek SB,“平滑”是指视觉上无法检测到任何角/尖等。 @sketchfemme,你是实时渲染线条,还是延迟渲染到收集一堆点之后? @Crashalot 我正在将这些点收集到一个数组中。您至少需要 4 分才能使用此算法。之后,您可以通过在每次调用 mouseMove 时清除屏幕来在画布上实时渲染 @sketchfemme:别忘了接受答案。 It's fine if it's your own. 【参考方案1】:将后续样本点与不相交的“curveTo”类型函数连接在一起的问题是曲线相遇的地方不平滑。这是因为两条曲线共享一个端点,但受完全不相交的控制点的影响。一种解决方案是“弯曲到”接下来的 2 个后续采样点之间的中点。使用这些新的插值点连接曲线可以在端点处实现平滑过渡(一次迭代的端点成为下一次迭代的控制点。)换句话说,两条不相交的曲线具有现在有更多共同点。
此解决方案摘自《Foundation ActionScript 3.0 Animation: Making things move》一书。 p.95 - 渲染技术:创建多条曲线。
注意:这个解决方案实际上并没有通过每个点来绘制,这是我的问题的标题(而是通过样本点近似曲线,但从不通过样本点),但出于我的目的(绘图应用程序),这对我来说已经足够好了,而且在视觉上你无法区分。 有解决方案可以遍历所有样本点,但要复杂得多(请参阅http://www.cartogrammar.com/blog/actionscript-curves-update/)
这里是近似法的绘制代码:
// move to the first point
ctx.moveTo(points[0].x, points[0].y);
for (i = 1; i < points.length - 2; i ++)
var xc = (points[i].x + points[i + 1].x) / 2;
var yc = (points[i].y + points[i + 1].y) / 2;
ctx.quadraticCurveTo(points[i].x, points[i].y, xc, yc);
// curve through the last two points
ctx.quadraticCurveTo(points[i].x, points[i].y, points[i+1].x,points[i+1].y);
【讨论】:
+1 这对我正在处理的 javascript/canvas 项目非常有用 很高兴能提供帮助。仅供参考,我已经启动了一个开源的 html5 画布绘图板,它是一个 jQuery 插件。这应该是一个有用的起点。 github.com/homanchou/sketchyPad 这很好,但你将如何制作曲线以使其通过所有点? 使用这种算法,每条连续曲线是否意味着从前一条曲线的终点开始? 非常感谢霍曼!有用!我花了这么多天来解决它。来自 Delphi android/ios 社区的您好!【参考方案2】:试一试 KineticJS - 你可以定义一个带有点数组的样条线。这是一个例子:
旧网址:http://www.html5canvastutorials.com/kineticjs/html5-canvas-kineticjs-spline-tutorial/
查看存档网址:https://web.archive.org/web/20141204030628/http://www.html5canvastutorials.com/kineticjs/html5-canvas-kineticjs-spline-tutorial/
【讨论】:
惊人的库!最适合这项任务的! 是的!我需要 blob() 函数来制作一个通过所有点的闭合形状。 404。找不到页面。 原始链接 - 404 未找到 - 请参阅 web.archive.org/web/20141204030628/http://…【参考方案3】:有点晚了,但为了记录。
您可以通过使用cardinal splines(又名规范样条)绘制穿过点的平滑曲线来实现平滑线条。
我为画布制作了这个功能 - 它分为三个功能以增加多功能性。主包装函数如下所示:
function drawCurve(ctx, ptsa, tension, isClosed, numOfSegments, showPoints)
showPoints = showPoints ? showPoints : false;
ctx.beginPath();
drawLines(ctx, getCurvePoints(ptsa, tension, isClosed, numOfSegments));
if (showPoints)
ctx.stroke();
ctx.beginPath();
for(var i=0;i<ptsa.length-1;i+=2)
ctx.rect(ptsa[i] - 2, ptsa[i+1] - 2, 4, 4);
要绘制曲线,有一个数组,其中 x、y 点的顺序为:x1,y1, x2,y2, ...xn,yn
。
像这样使用它:
var myPoints = [10,10, 40,30, 100,10]; //minimum two points
var tension = 1;
drawCurve(ctx, myPoints); //default tension=0.5
drawCurve(ctx, myPoints, tension);
上面的函数调用了两个子函数,一个是计算平滑点。这将返回一个包含新点的数组 - 这是计算平滑点的核心函数:
function getCurvePoints(pts, tension, isClosed, numOfSegments)
// use input value if provided, or use a default value
tension = (typeof tension != 'undefined') ? tension : 0.5;
isClosed = isClosed ? isClosed : false;
numOfSegments = numOfSegments ? numOfSegments : 16;
var _pts = [], res = [], // clone array
x, y, // our x,y coords
t1x, t2x, t1y, t2y, // tension vectors
c1, c2, c3, c4, // cardinal points
st, t, i; // steps based on num. of segments
// clone array so we don't change the original
//
_pts = pts.slice(0);
// The algorithm require a previous and next point to the actual point array.
// Check if we will draw closed or open curve.
// If closed, copy end points to beginning and first points to end
// If open, duplicate first points to befinning, end points to end
if (isClosed)
_pts.unshift(pts[pts.length - 1]);
_pts.unshift(pts[pts.length - 2]);
_pts.unshift(pts[pts.length - 1]);
_pts.unshift(pts[pts.length - 2]);
_pts.push(pts[0]);
_pts.push(pts[1]);
else
_pts.unshift(pts[1]); //copy 1. point and insert at beginning
_pts.unshift(pts[0]);
_pts.push(pts[pts.length - 2]); //copy last point and append
_pts.push(pts[pts.length - 1]);
// ok, lets start..
// 1. loop goes through point array
// 2. loop goes through each segment between the 2 pts + 1e point before and after
for (i=2; i < (_pts.length - 4); i+=2)
for (t=0; t <= numOfSegments; t++)
// calc tension vectors
t1x = (_pts[i+2] - _pts[i-2]) * tension;
t2x = (_pts[i+4] - _pts[i]) * tension;
t1y = (_pts[i+3] - _pts[i-1]) * tension;
t2y = (_pts[i+5] - _pts[i+1]) * tension;
// calc step
st = t / numOfSegments;
// calc cardinals
c1 = 2 * Math.pow(st, 3) - 3 * Math.pow(st, 2) + 1;
c2 = -(2 * Math.pow(st, 3)) + 3 * Math.pow(st, 2);
c3 = Math.pow(st, 3) - 2 * Math.pow(st, 2) + st;
c4 = Math.pow(st, 3) - Math.pow(st, 2);
// calc x and y cords with common control vectors
x = c1 * _pts[i] + c2 * _pts[i+2] + c3 * t1x + c4 * t2x;
y = c1 * _pts[i+1] + c2 * _pts[i+3] + c3 * t1y + c4 * t2y;
//store points in array
res.push(x);
res.push(y);
return res;
并将点实际绘制为平滑曲线(或任何其他分段线,只要您有 x,y 数组):
function drawLines(ctx, pts)
ctx.moveTo(pts[0], pts[1]);
for(i=2;i<pts.length-1;i+=2) ctx.lineTo(pts[i], pts[i+1]);
var ctx = document.getElementById("c").getContext("2d");
function drawCurve(ctx, ptsa, tension, isClosed, numOfSegments, showPoints)
ctx.beginPath();
drawLines(ctx, getCurvePoints(ptsa, tension, isClosed, numOfSegments));
if (showPoints)
ctx.beginPath();
for(var i=0;i<ptsa.length-1;i+=2)
ctx.rect(ptsa[i] - 2, ptsa[i+1] - 2, 4, 4);
ctx.stroke();
var myPoints = [10,10, 40,30, 100,10, 200, 100, 200, 50, 250, 120]; //minimum two points
var tension = 1;
drawCurve(ctx, myPoints); //default tension=0.5
drawCurve(ctx, myPoints, tension);
function getCurvePoints(pts, tension, isClosed, numOfSegments)
// use input value if provided, or use a default value
tension = (typeof tension != 'undefined') ? tension : 0.5;
isClosed = isClosed ? isClosed : false;
numOfSegments = numOfSegments ? numOfSegments : 16;
var _pts = [], res = [], // clone array
x, y, // our x,y coords
t1x, t2x, t1y, t2y, // tension vectors
c1, c2, c3, c4, // cardinal points
st, t, i; // steps based on num. of segments
// clone array so we don't change the original
//
_pts = pts.slice(0);
// The algorithm require a previous and next point to the actual point array.
// Check if we will draw closed or open curve.
// If closed, copy end points to beginning and first points to end
// If open, duplicate first points to befinning, end points to end
if (isClosed)
_pts.unshift(pts[pts.length - 1]);
_pts.unshift(pts[pts.length - 2]);
_pts.unshift(pts[pts.length - 1]);
_pts.unshift(pts[pts.length - 2]);
_pts.push(pts[0]);
_pts.push(pts[1]);
else
_pts.unshift(pts[1]); //copy 1. point and insert at beginning
_pts.unshift(pts[0]);
_pts.push(pts[pts.length - 2]); //copy last point and append
_pts.push(pts[pts.length - 1]);
// ok, lets start..
// 1. loop goes through point array
// 2. loop goes through each segment between the 2 pts + 1e point before and after
for (i=2; i < (_pts.length - 4); i+=2)
for (t=0; t <= numOfSegments; t++)
// calc tension vectors
t1x = (_pts[i+2] - _pts[i-2]) * tension;
t2x = (_pts[i+4] - _pts[i]) * tension;
t1y = (_pts[i+3] - _pts[i-1]) * tension;
t2y = (_pts[i+5] - _pts[i+1]) * tension;
// calc step
st = t / numOfSegments;
// calc cardinals
c1 = 2 * Math.pow(st, 3) - 3 * Math.pow(st, 2) + 1;
c2 = -(2 * Math.pow(st, 3)) + 3 * Math.pow(st, 2);
c3 = Math.pow(st, 3) - 2 * Math.pow(st, 2) + st;
c4 = Math.pow(st, 3) - Math.pow(st, 2);
// calc x and y cords with common control vectors
x = c1 * _pts[i] + c2 * _pts[i+2] + c3 * t1x + c4 * t2x;
y = c1 * _pts[i+1] + c2 * _pts[i+3] + c3 * t1y + c4 * t2y;
//store points in array
res.push(x);
res.push(y);
return res;
function drawLines(ctx, pts)
ctx.moveTo(pts[0], pts[1]);
for(i=2;i<pts.length-1;i+=2) ctx.lineTo(pts[i], pts[i+1]);
canvas border: 1px solid red;
<canvas id="c"><canvas>
这会导致:
您可以轻松地扩展画布,以便改为这样调用它:
ctx.drawCurve(myPoints);
在 javascript 中添加以下内容:
if (CanvasRenderingContext2D != 'undefined')
CanvasRenderingContext2D.prototype.drawCurve =
function(pts, tension, isClosed, numOfSegments, showPoints)
drawCurve(this, pts, tension, isClosed, numOfSegments, showPoints)
您可以在 NPM (npm i cardinal-spline-js
) 或 GitLab 上找到更优化的版本。
【讨论】:
首先:这太棒了。 :-) 但是看看那张图片,它是否给人一种(误导性的)印象,即在#9 和#10 之间的途中值实际上低于#10 的值? (我从我能看到的实际点数数,所以#1将是靠近初始下降轨迹顶部的那个,#2是最底部的那个[图中的最低点],依此类推...... ) 只想说,经过几天的搜索,这是唯一真正完全按照我想要的方式工作的实用程序。非常感谢 是 是 是 谢谢!我跳起来高兴地跳舞。 @T.J.Crowder(抱歉有点(?!)后期跟进:))下降是张力计算的结果。为了以正确的角度/方向“击中”下一个点,张力迫使曲线下降,因此它可以继续以正确的角度进行下一段(角度在这里可能不是一个好词,我的英语缺乏...... .)。使用前两个点和后两个点计算张力。所以简而言之:不,它不代表任何实际数据,只是计算张力。 很久以前你发布了这个解决方案,你今天帮我解决了一个大问题。非常感谢!【参考方案4】:作为Daniel Howard points out,Rob Spencer 在http://scaledinnovation.com/analytics/splines/aboutSplines.html 描述了您想要什么。
这是一个交互式演示:http://jsbin.com/ApitIxo/2/
这里是一个 sn-p,以防 jsbin 宕机。
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>Demo smooth connection</title>
</head>
<body>
<div id="display">
Click to build a smooth path.
(See Rob Spencer's <a href="http://scaledinnovation.com/analytics/splines/aboutSplines.html">article</a>)
<br><label><input type="checkbox" id="showPoints" checked> Show points</label>
<br><label><input type="checkbox" id="showControlLines" checked> Show control lines</label>
<br>
<label>
<input type="range" id="tension" min="-1" max="2" step=".1" value=".5" > Tension <span id="tensionvalue">(0.5)</span>
</label>
<div id="mouse"></div>
</div>
<canvas id="canvas"></canvas>
<style>
html position: relative; height: 100%; width: 100%;
body position: absolute; left: 0; right: 0; top: 0; bottom: 0;
canvas outline: 1px solid red;
#display position: fixed; margin: 8px; background: white; z-index: 1;
</style>
<script>
function update()
$("tensionvalue").innerHTML="("+$("tension").value+")";
drawSplines();
$("showPoints").onchange = $("showControlLines").onchange = $("tension").onchange = update;
// utility function
function $(id) return document.getElementById(id);
var canvas=$("canvas"), ctx=canvas.getContext("2d");
function setCanvasSize()
canvas.width = parseInt(window.getComputedStyle(document.body).width);
canvas.height = parseInt(window.getComputedStyle(document.body).height);
window.onload = window.onresize = setCanvasSize();
function mousePositionOnCanvas(e)
var el=e.target, c=el;
var scaleX = c.width/c.offsetWidth || 1;
var scaleY = c.height/c.offsetHeight || 1;
if (!isNaN(e.offsetX))
return x:e.offsetX*scaleX, y:e.offsetY*scaleY ;
var x=e.pageX, y=e.pageY;
do
x -= el.offsetLeft;
y -= el.offsetTop;
el = el.offsetParent;
while (el);
return x: x*scaleX, y: y*scaleY ;
canvas.onclick = function(e)
var p = mousePositionOnCanvas(e);
addSplinePoint(p.x, p.y);
;
function drawPoint(x,y,color)
ctx.save();
ctx.fillStyle=color;
ctx.beginPath();
ctx.arc(x,y,3,0,2*Math.PI);
ctx.fill()
ctx.restore();
canvas.onmousemove = function(e)
var p = mousePositionOnCanvas(e);
$("mouse").innerHTML = p.x+","+p.y;
;
var pts=[]; // a list of x and ys
// given an array of x,y's, return distance between any two,
// note that i and j are indexes to the points, not directly into the array.
function dista(arr, i, j)
return Math.sqrt(Math.pow(arr[2*i]-arr[2*j], 2) + Math.pow(arr[2*i+1]-arr[2*j+1], 2));
// return vector from i to j where i and j are indexes pointing into an array of points.
function va(arr, i, j)
return [arr[2*j]-arr[2*i], arr[2*j+1]-arr[2*i+1]]
function ctlpts(x1,y1,x2,y2,x3,y3)
var t = $("tension").value;
var v = va(arguments, 0, 2);
var d01 = dista(arguments, 0, 1);
var d12 = dista(arguments, 1, 2);
var d012 = d01 + d12;
return [x2 - v[0] * t * d01 / d012, y2 - v[1] * t * d01 / d012,
x2 + v[0] * t * d12 / d012, y2 + v[1] * t * d12 / d012 ];
function addSplinePoint(x, y)
pts.push(x); pts.push(y);
drawSplines();
function drawSplines()
clear();
cps = []; // There will be two control points for each "middle" point, 1 ... len-2e
for (var i = 0; i < pts.length - 2; i += 1)
cps = cps.concat(ctlpts(pts[2*i], pts[2*i+1],
pts[2*i+2], pts[2*i+3],
pts[2*i+4], pts[2*i+5]));
if ($("showControlLines").checked) drawControlPoints(cps);
if ($("showPoints").checked) drawPoints(pts);
drawCurvedPath(cps, pts);
function drawControlPoints(cps)
for (var i = 0; i < cps.length; i += 4)
showPt(cps[i], cps[i+1], "pink");
showPt(cps[i+2], cps[i+3], "pink");
drawLine(cps[i], cps[i+1], cps[i+2], cps[i+3], "pink");
function drawPoints(pts)
for (var i = 0; i < pts.length; i += 2)
showPt(pts[i], pts[i+1], "black");
function drawCurvedPath(cps, pts)
var len = pts.length / 2; // number of points
if (len < 2) return;
if (len == 2)
ctx.beginPath();
ctx.moveTo(pts[0], pts[1]);
ctx.lineTo(pts[2], pts[3]);
ctx.stroke();
else
ctx.beginPath();
ctx.moveTo(pts[0], pts[1]);
// from point 0 to point 1 is a quadratic
ctx.quadraticCurveTo(cps[0], cps[1], pts[2], pts[3]);
// for all middle points, connect with bezier
for (var i = 2; i < len-1; i += 1)
// console.log("to", pts[2*i], pts[2*i+1]);
ctx.bezierCurveTo(
cps[(2*(i-1)-1)*2], cps[(2*(i-1)-1)*2+1],
cps[(2*(i-1))*2], cps[(2*(i-1))*2+1],
pts[i*2], pts[i*2+1]);
ctx.quadraticCurveTo(
cps[(2*(i-1)-1)*2], cps[(2*(i-1)-1)*2+1],
pts[i*2], pts[i*2+1]);
ctx.stroke();
function clear()
ctx.save();
// use alpha to fade out
ctx.fillStyle = "rgba(255,255,255,.7)"; // clear screen
ctx.fillRect(0,0,canvas.width,canvas.height);
ctx.restore();
function showPt(x,y,fillStyle)
ctx.save();
ctx.beginPath();
if (fillStyle)
ctx.fillStyle = fillStyle;
ctx.arc(x, y, 5, 0, 2*Math.PI);
ctx.fill();
ctx.restore();
function drawLine(x1, y1, x2, y2, strokeStyle)
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
if (strokeStyle)
ctx.save();
ctx.strokeStyle = strokeStyle;
ctx.stroke();
ctx.restore();
else
ctx.save();
ctx.strokeStyle = "pink";
ctx.stroke();
ctx.restore();
</script>
</body>
</html>
【讨论】:
【参考方案5】:为了添加到 K3N 的基数样条方法并可能解决 T. J. Crowder 对曲线在误导性位置“倾斜”的担忧,我在 getCurvePoints()
函数中插入了以下代码,就在 res.push(x);
之前
if ((y < _pts[i+1] && y < _pts[i+3]) || (y > _pts[i+1] && y > _pts[i+3]))
y = (_pts[i+1] + _pts[i+3]) / 2;
if ((x < _pts[i] && x < _pts[i+2]) || (x > _pts[i] && x > _pts[i+2]))
x = (_pts[i] + _pts[i+2]) / 2;
这有效地在每对连续点之间创建了一个(不可见的)边界框,并确保曲线保持在该边界框内 - 即。如果曲线上的一个点在两个点的上方/下方/左侧/右侧,它会将其位置更改为在框内。这里使用了中点,但这可以改进,也许使用线性插值。
【讨论】:
【参考方案6】:我决定添加,而不是将我的解决方案发布到另一个帖子。 以下是我构建的解决方案,可能并不完美,但目前输出还不错。
重要:它会通过所有的点!
如果您有任何想法,让它变得更好,请分享给我。谢谢。
以下是前后对比:
将此代码保存到 HTML 以进行测试。
<!DOCTYPE html>
<html>
<body>
<canvas id="myCanvas" style="border:1px solid #d3d3d3;">Your browser does not support the HTML5 canvas tag.</canvas>
<script>
var cv = document.getElementById("myCanvas");
var ctx = cv.getContext("2d");
function gradient(a, b)
return (b.y-a.y)/(b.x-a.x);
function bzCurve(points, f, t)
//f = 0, will be straight line
//t suppose to be 1, but changing the value can control the smoothness too
if (typeof(f) == 'undefined') f = 0.3;
if (typeof(t) == 'undefined') t = 0.6;
ctx.beginPath();
ctx.moveTo(points[0].x, points[0].y);
var m = 0;
var dx1 = 0;
var dy1 = 0;
var preP = points[0];
for (var i = 1; i < points.length; i++)
var curP = points[i];
nexP = points[i + 1];
if (nexP)
m = gradient(preP, nexP);
dx2 = (nexP.x - curP.x) * -f;
dy2 = dx2 * m * t;
else
dx2 = 0;
dy2 = 0;
ctx.bezierCurveTo(preP.x - dx1, preP.y - dy1, curP.x + dx2, curP.y + dy2, curP.x, curP.y);
dx1 = dx2;
dy1 = dy2;
preP = curP;
ctx.stroke();
// Generate random data
var lines = [];
var X = 10;
var t = 40; //to control width of X
for (var i = 0; i < 100; i++ )
Y = Math.floor((Math.random() * 300) + 50);
p = x: X, y: Y ;
lines.push(p);
X = X + t;
//draw straight line
ctx.beginPath();
ctx.setLineDash([5]);
ctx.lineWidth = 1;
bzCurve(lines, 0, 1);
//draw smooth line
ctx.setLineDash([0]);
ctx.lineWidth = 2;
ctx.strokeStyle = "blue";
bzCurve(lines, 0.3, 1);
</script>
</body>
</html>
【讨论】:
【参考方案7】:第一个答案不会通过所有点。该图将准确地通过所有点,并且将是一个完美的曲线,其中的点为 [x:,y:] n 个这样的点。
var points = [x:1,y:1,x:2,y:3,x:3,y:4,x:4,y:2,x:5,y:6] //took 5 example points
ctx.moveTo((points[0].x), points[0].y);
for(var i = 0; i < points.length-1; i ++)
var x_mid = (points[i].x + points[i+1].x) / 2;
var y_mid = (points[i].y + points[i+1].y) / 2;
var cp_x1 = (x_mid + points[i].x) / 2;
var cp_x2 = (x_mid + points[i+1].x) / 2;
ctx.quadraticCurveTo(cp_x1,points[i].y ,x_mid, y_mid);
ctx.quadraticCurveTo(cp_x2,points[i+1].y ,points[i+1].x,points[i+1].y);
【讨论】:
这是迄今为止最简单、最正确的方法。 它没有为我绘制任何东西。除了.getContext('2d')
,我还需要什么【参考方案8】:
我发现这个很好用
function drawCurve(points, tension)
ctx.beginPath();
ctx.moveTo(points[0].x, points[0].y);
var t = (tension != null) ? tension : 1;
for (var i = 0; i < points.length - 1; i++)
var p0 = (i > 0) ? points[i - 1] : points[0];
var p1 = points[i];
var p2 = points[i + 1];
var p3 = (i != points.length - 2) ? points[i + 2] : p2;
var cp1x = p1.x + (p2.x - p0.x) / 6 * t;
var cp1y = p1.y + (p2.y - p0.y) / 6 * t;
var cp2x = p2.x - (p3.x - p1.x) / 6 * t;
var cp2y = p2.y - (p3.y - p1.y) / 6 * t;
ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, p2.x, p2.y);
ctx.stroke();
【讨论】:
【参考方案9】:令人难以置信的迟到但受到 Homan 出色简单答案的启发,请允许我发布一个更通用的解决方案(一般是指 Homan 的解决方案在少于 3 个顶点的点数组上崩溃):
function smooth(ctx, points)
if(points == undefined || points.length == 0)
return true;
if(points.length == 1)
ctx.moveTo(points[0].x, points[0].y);
ctx.lineTo(points[0].x, points[0].y);
return true;
if(points.length == 2)
ctx.moveTo(points[0].x, points[0].y);
ctx.lineTo(points[1].x, points[1].y);
return true;
ctx.moveTo(points[0].x, points[0].y);
for (var i = 1; i < points.length - 2; i ++)
var xc = (points[i].x + points[i + 1].x) / 2;
var yc = (points[i].y + points[i + 1].y) / 2;
ctx.quadraticCurveTo(points[i].x, points[i].y, xc, yc);
ctx.quadraticCurveTo(points[i].x, points[i].y, points[i+1].x, points[i+1].y);
【讨论】:
【参考方案10】:如果你想通过n个点确定曲线的方程,那么下面的代码会给你n-1次多项式的系数,并将这些系数保存到coefficients[]
数组中(从常数项开始) . x 坐标不必按顺序排列。这是Lagrange polynomial 的示例。
var xPoints=[2,4,3,6,7,10]; //example coordinates
var yPoints=[2,5,-2,0,2,8];
var coefficients=[];
for (var m=0; m<xPoints.length; m++) coefficients[m]=0;
for (var m=0; m<xPoints.length; m++)
var newCoefficients=[];
for (var nc=0; nc<xPoints.length; nc++) newCoefficients[nc]=0;
if (m>0)
newCoefficients[0]=-xPoints[0]/(xPoints[m]-xPoints[0]);
newCoefficients[1]=1/(xPoints[m]-xPoints[0]);
else
newCoefficients[0]=-xPoints[1]/(xPoints[m]-xPoints[1]);
newCoefficients[1]=1/(xPoints[m]-xPoints[1]);
var startIndex=1;
if (m==0) startIndex=2;
for (var n=startIndex; n<xPoints.length; n++)
if (m==n) continue;
for (var nc=xPoints.length-1; nc>=1; nc--)
newCoefficients[nc]=newCoefficients[nc]*(-xPoints[n]/(xPoints[m]-xPoints[n]))+newCoefficients[nc-1]/(xPoints[m]-xPoints[n]);
newCoefficients[0]=newCoefficients[0]*(-xPoints[n]/(xPoints[m]-xPoints[n]));
for (var nc=0; nc<xPoints.length; nc++) coefficients[nc]+=yPoints[m]*newCoefficients[nc];
【讨论】:
【参考方案11】:这段代码非常适合我:
this.context.beginPath();
this.context.moveTo(data[0].x, data[0].y);
for (let i = 1; i < data.length; i++)
this.context.bezierCurveTo(
data[i - 1].x + (data[i].x - data[i - 1].x) / 2,
data[i - 1].y,
data[i - 1].x + (data[i].x - data[i - 1].x) / 2,
data[i].y,
data[i].x,
data[i].y);
你有正确的平滑线和正确的端点 注意! (y = "画布高度" - y);
【讨论】:
非常好,谢谢。但是你错过了最后画线的实际命令:this.context.stroke()【参考方案12】:对原始问题的回答略有不同;
如果有人想画一个形状:
由一系列点描述 线条在点处有一条小曲线 线不一定要通过通过点(即稍微通过它们的“内部”)那么希望我的以下功能可以帮助
<!DOCTYPE html>
<html>
<body>
<canvas id="myCanvas" style="border: 1px solid #d3d3d3">Your browser does not support the
HTML5 canvas tag.</canvas>
<script>
var cv = document.getElementById("myCanvas");
var ctx = cv.getContext("2d");
const drawPointsWithCurvedCorners = (points, ctx) =>
for (let n = 0; n <= points.length - 1; n++)
let pointA = points[n];
let pointB = points[(n + 1) % points.length];
let pointC = points[(n + 2) % points.length];
const midPointAB =
x: pointA.x + (pointB.x - pointA.x) / 2,
y: pointA.y + (pointB.y - pointA.y) / 2,
;
const midPointBC =
x: pointB.x + (pointC.x - pointB.x) / 2,
y: pointB.y + (pointC.y - pointB.y) / 2,
;
ctx.moveTo(midPointAB.x, midPointAB.y);
ctx.arcTo(
pointB.x,
pointB.y,
midPointBC.x,
midPointBC.y,
radii[pointB.r]
);
ctx.lineTo(midPointBC.x, midPointBC.y);
;
const shapeWidth = 200;
const shapeHeight = 150;
const topInsetDepth = 35;
const topInsetSideWidth = 20;
const topInsetHorizOffset = shapeWidth * 0.25;
const radii =
small: 15,
large: 30,
;
const points = [
// TOP-LEFT
x: 0,
y: 0,
r: "large",
,
x: topInsetHorizOffset,
y: 0,
r: "small",
,
x: topInsetHorizOffset + topInsetSideWidth,
y: topInsetDepth,
r: "small",
,
x: shapeWidth - (topInsetHorizOffset + topInsetSideWidth),
y: topInsetDepth,
r: "small",
,
x: shapeWidth - topInsetHorizOffset,
y: 0,
r: "small",
,
// TOP-RIGHT
x: shapeWidth,
y: 0,
r: "large",
,
// BOTTOM-RIGHT
x: shapeWidth,
y: shapeHeight,
r: "large",
,
// BOTTOM-LEFT
x: 0,
y: shapeHeight,
r: "large",
,
];
// ACTUAL DRAWING OF POINTS
ctx.beginPath();
drawPointsWithCurvedCorners(points, ctx);
ctx.stroke();
</script>
</body>
</html>
【讨论】:
【参考方案13】:你好
我很欣赏 user1693593 的解决方案:Hermite 多项式似乎是控制将要绘制的内容的最佳方法,并且从数学角度来看也是最令人满意的。 这个话题似乎已经关闭了很长时间,但可能像我这样的一些后来者仍然对它感兴趣。 我一直在寻找一个免费的交互式绘图生成器,它可以让我存储曲线并在其他任何地方重复使用,但在网络上没有找到这种东西:所以我以自己的方式制作了它,来自 wikipedia 来源由 user1693593 提及。 在这里很难解释它是如何工作的,而了解它是否值得的最好方法是查看 https://sites.google.com/view/divertissements/accueil/splines。
【讨论】:
以上是关于如何使用javascript HTML5画布通过N个点绘制平滑曲线?的主要内容,如果未能解决你的问题,请参考以下文章