7

我想在 html5 画布中画一个椭圆,我在stackoverflow中找到了一个很好的方法。但我还有另一个问题。

function drawEllipse(ctx, x, y, w, h) {
  var kappa = 0.5522848;
      ox = (w / 2) * kappa, // control point offset horizontal
      oy = (h / 2) * kappa, // control point offset vertical
      xe = x + w,           // x-end
      ye = y + h,           // y-end
      xm = x + w / 2,       // x-middle
      ym = y + h / 2;       // y-middle

  ctx.beginPath();
  ctx.moveTo(x, ym);
  ctx.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);
  ctx.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);
  ctx.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);
  ctx.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);
  ctx.closePath();
  ctx.stroke();
}

上面链接中的方法使用 bezierCurveTo 绘制椭圆,但它绘制了 bezierCurveTo 4次。但我认为只有2 bezierCurveTo 可以画一个椭圆。像这样:

在此处输入图像描述

但是我数学很弱,谁能告诉我“控制点”和“椭圆点”的关系吗?或者我们必须绘制四个贝塞尔曲线来绘制椭圆?

谢谢大家

4

5 回答 5

6

我的背景不是数学,所以如果我错了,我相信有人会纠正我,但根据我的理解,我们可以用两条三次贝塞尔曲线绘制一个很好的椭圆近似值,坐标会有点棘手.

要回答您关于椭圆点和控制点之间关系的问题,我认为最好通过从我选择的点观看此视频来回答,如果您熟悉插值,或者如果您不熟悉则从头开始。不用担心它很短。

我们可能会遇到的一个问题是,当我们从顶部开始并以矩形的角(具有相同的宽度和高度)作为控制点的椭圆底部执行 bezierCurveTo 时,椭圆的宽度为将小于矩形。我们想要的大小的 0.75 倍。所以我们可以相应地缩放控制点。

我们的控制点的 x 将像这样调整(假设宽度是椭圆的宽度,我们除以 2 以获得它与原点的偏移量)

var cpx = (width / .75) / 2;

将可视化放在一起,您可以在其中使用宽度和高度并查看绘制的椭圆。

红色椭圆是我们想要的绘制方式,如果我们不重新定位控制点,内部的椭圆将如何绘制。这些线条说明了视频中显示的 De Casteljau 的算法。

这是可视化的屏幕截图 在此处输入图像描述

于 2013-01-05T11:15:07.373 回答
2

您只需要两条三次贝塞尔曲线即可绘制椭圆。这是 DerekR 代码的简化版本,它使用您提供的原始函数参数——假设您希望 x 和 y 成为椭圆的中心:

jsFiddle

function drawEllipse(ctx, x, y, w, h) {
    var width_over_2 = w / 2;
    var width_two_thirds = w * 2 / 3;
    var height_over_2 = h / 2;

    ctx.beginPath();
    ctx.moveTo(x, y - height_over_2);
    ctx.bezierCurveTo(x + width_two_thirds, y - height_over_2, x + width_two_thirds, y + height_over_2, x, y + height_over_2);
    ctx.bezierCurveTo(x - width_two_thirds, y + height_over_2, x - width_two_thirds, y - height_over_2, x, y -height_over_2);
    ctx.closePath();
    ctx.stroke();
}
于 2013-12-14T10:31:52.783 回答
2

非常感谢 BKH。我用他的代码和两条贝塞尔曲线来完成我的椭圆绘制,任何旋转角度。另外,我创建了一个贝塞尔曲线绘制的椭圆和原生 ellipse() 函数(目前仅在 Chrome 中实现)之间的比较演示。

function drawEllipseByBezierCurves(ctx, x, y, radiusX, radiusY, rotationAngle) {
var width_two_thirds = radiusX * 4 / 3;

var dx1 = Math.sin(rotationAngle) * radiusY;
var dy1 = Math.cos(rotationAngle) * radiusY;
var dx2 = Math.cos(rotationAngle) * width_two_thirds;
var dy2 = Math.sin(rotationAngle) * width_two_thirds;

var topCenterX = x - dx1;
var topCenterY = y + dy1;
var topRightX = topCenterX + dx2;
var topRightY = topCenterY + dy2;
var topLeftX = topCenterX - dx2;
var topLeftY = topCenterY - dy2;

var bottomCenterX = x + dx1;
var bottomCenterY = y - dy1;
var bottomRightX = bottomCenterX + dx2;
var bottomRightY = bottomCenterY + dy2;
var bottomLeftX = bottomCenterX - dx2;
var bottomLeftY = bottomCenterY - dy2;

ctx.beginPath();
ctx.moveTo(bottomCenterX, bottomCenterY);
ctx.bezierCurveTo(bottomRightX, bottomRightY, topRightX, topRightY, topCenterX, topCenterY);
ctx.bezierCurveTo(topLeftX, topLeftY, bottomLeftX, bottomLeftY, bottomCenterX, bottomCenterY);
ctx.closePath();
ctx.stroke();

}

于 2014-02-26T08:30:59.127 回答
1

您会在http://pomax.github.io/bezierinfo/#circles_cubic中发现这基于数学的解释稍微多一些,但要点是使用三次贝塞尔曲线超过四分之一圈通常不是一个好主意。值得庆幸的是,使用四条曲线可以很容易地找到所需的控制点。从一个圆开始,在这种情况下,每个四分之一圆是 (1,0)--(1,0.55228)--(0.55228,1)--(0,1) 并带有椭圆的缩放坐标。用 +/- 符号交换四次以形成一个完整的圆圈,缩放尺寸以获得椭圆,然后完成。

如果您使用两条曲线,则坐标变为 (1,0)--(1,4/3)--(-1,4/3)--(-1,0),按椭圆缩放。它在您的应用程序中可能看起来仍然足够体面,这在一定程度上取决于您的绘图最终有多大。

于 2013-04-19T11:59:03.943 回答
0

可以用数学证明,任何度数的贝塞尔曲线都不能做圆。您可以通过近似来制作“几乎圆形”。

假设您想在 [0,0] 周围绘制四分之一圆。三次贝塞尔坐标为:

[0   , 1   ]
[0.55, 1   ]
[1   , 0.55]
[1   , 0   ]

这是一个非常好的近似值。对其进行线性变换以获得椭圆。

于 2013-11-07T17:54:54.623 回答