在 canvas 中计算同心圆弧

Calculating a concentric arc in canvas

我正在尝试根据给定弧(二次贝塞尔曲线)计算 2 个同心圆弧(三次贝塞尔曲线)。我想我可以在 1/3 和 2/3 处计算立方体的控制点,但它并不完全匹配。

  var u = 1 / 3; // fraction of curve where Px1 and Py1 are 
  var v = 2 / 3; // fraction of curve where Px2 and Py2 are 
  //Calculate control points (Cx1, Cy1, Cx2, Cy2)
  var a = 3 * (1 - u) * (1 - u) * u;
  var b = 3 * (1 - u) * u * u;
  var c = 3 * (1 - v) * (1 - v) * v;
  var d = 3 * (1 - v) * v * v;
  var det = a * d - b * c;
  var Qx1 = Px1 - ((1 - u) * (1 - u) * (1 - u) * Px0 + u * u * u * Px3);
  var Qy1 = Py1 - ((1 - u) * (1 - u) * (1 - u) * Py0 + u * u * u * Py3);
  var Qx2 = Px2 - ((1 - v) * (1 - v) * (1 - v) * Px0 + v * v * v * Px3);
  var Qy2 = Py2 - ((1 - v) * (1 - v) * (1 - v) * Py0 + v * v * v * Py3);
  var Cx1 = (d * Qx1 - b * Qx2) / det;
  var Cy1 = (d * Qy1 - b * Qy2) / det;
  var Cx2 = ((-c) * Qx1 + a * Qx2) / det;
  var Cy2 = ((-c) * Qy1 + a * Qy2) / det;
  ctx.beginPath();
  ctx.moveTo(Px0, Py0);
  ctx.bezierCurveTo(Cx1, Cy1, Cx2, Cy2, Px3, Py3);
  ctx.strokeStyle = "#0000FF";
  ctx.stroke();

控制点是否也取决于弧的半径或完全不同的东西?三次贝塞尔曲线甚至是绘制同心圆弧的好选择吗?二次贝塞尔曲线绝对行不通,三次贝塞尔曲线绝对让我更接近我需要的东西。

这是 link: http://codepen.io/davidreed0/full/zGqPxQ/

使用位置滑块移动椭圆。

目前的问题对要求有点不清楚。在任何情况下,这都是一种不需要太多计算的方法,但利用绘图操作来可视化与代码笔中显示的大致相同的内容。

主要步骤是:

  • 在屏幕外 canvas:
  • 定义一条线宽,半径设置为绿色区域
  • 定义直线的圆头
  • 用纯色绘制贝塞尔线
  • 将结果绘制到主 canvas 中,相对于蓝线的粗细进行各种偏移。
  • 清除中心,您将得到蓝色轮廓
  • 实施手动贝塞尔曲线,以便您可以在该形状内的任意点绘制绿色 arc/ellipse

Radius/diameter可以展开。如果您需要可变半径,您可以使用贝塞尔公式绘制一系列彼此重叠的蓝色弧线。

概念验证

这将逐步显示该过程。

第 1 步

在屏幕外 canvas(此处显示在屏幕上用于演示,我们将在下一步切换):

var c = document.querySelector("canvas"),
    ctx = c.getContext("2d"),
    dia = 90;                                        // diameter of graphics

ctx.strokeStyle = "blue";                            // color
ctx.lineWidth = dia;                                 // line-width = dia
ctx.lineCap = "round";                               // round caps

// draw bezier (quadratic, one control point)
ctx.moveTo(dia, dia);
ctx.quadraticCurveTo(300, 230, c.width - dia, dia);
ctx.stroke();
<canvas width=600 height=300></canvas>

完成。我们现在有了主要形状。根据需要调整分数。

第 2 步

因为我们现在有了主要形状,所以我们将使用这个形状创建轮廓:

  • 将其绘制到主要区域 canvas 偏移它的圆圈(f.ex。主要区域周围的 8 个位置)
  • 使用补偿击倒中心。模式“目的地输出”只留下轮廓

var c = document.querySelector("canvas"),
    co = document.createElement("canvas"),
    ctx = c.getContext("2d"),
    ctxo = co.getContext("2d"),
    dia = 90;

co.width = c.width;
co.height = c.height;

ctxo.strokeStyle = "blue";
ctxo.lineWidth = dia;
ctxo.lineCap = "round";

// draw bezier (quadratic here, one control point)
ctxo.moveTo(dia, dia);
ctxo.quadraticCurveTo(300, 230, c.width - dia, dia);
ctxo.stroke();

// draw multiple times to main canvas
var thickness = 1, angle = 0, step = Math.PI * 0.25;

for(; angle < Math.PI * 2; angle += step) {
  var x = thickness * Math.cos(angle),
      y = thickness * Math.sin(angle);
  ctx.drawImage(co, x, y);
}

// punch out center
ctx.globalCompositeOperation = "destination-out";
ctx.drawImage(co, 0, 0);
<canvas width=600 height=300></canvas>

步骤 3

使用 canvas 的自定义实现绘制绿色圆圈。首先,我们备份生成的蓝色轮廓的副本,以便我们可以在自由圆的顶部重新绘制它。我们可以为此重用离线 canvas,只需清除它并返回结果(重置转换):

我们在这里唯一需要的计算是二次贝塞尔曲线,我们在 [0, 1] 范围内提供 t 以获得一个点:

function getQuadraticPoint(z0x, z0y, cx, cy, z1x, z1y, t) {

  var t1 = (1 - t),       // (1 - t)
      t12 = t1 * t1,      // (1 - t) ^ 2
      t2 = t * t,         // t ^ 2
      t21tt = 2 * t1 * t; // 2(1-t)t

  return {
    x: t12 * z0x + t21tt * cx + t2 * z1x,
    y: t12 * z0y + t21tt * cy + t2 * z1y
  }
}

结果将是(使用更接近原始代码笔的值):

var c = document.querySelector("canvas"),
    co = document.createElement("canvas"),
    ctx = c.getContext("2d"),
    ctxo = co.getContext("2d"),
    radius = 150,
    dia = radius * 2;

co.width = c.width;
co.height = c.height;

ctxo.translate(2,2);           // to avoid clipping of edges in this demo
ctxo.strokeStyle = "blue";
ctxo.lineWidth = dia;
ctxo.lineCap = "round";

// draw bezier (quadratic here, one control point)
ctxo.moveTo(radius, radius);
ctxo.quadraticCurveTo(300, 230, c.width - radius - 6, radius);
ctxo.stroke();

// draw multiple times to main canvas
var thickness = 1, angle = 0, step = Math.PI * 0.25;
for(; angle < Math.PI * 2; angle += step) {
  var x = thickness * Math.cos(angle),
      y = thickness * Math.sin(angle);
  ctx.drawImage(co, x, y);
}

// punch out center
ctx.globalCompositeOperation = "destination-out";
ctx.drawImage(co, 0, 0);

// back-up result by reusing off-screen canvas
ctxo.clearRect(0, 0, co.width, co.height);
ctxo.drawImage(c, 0, 0);

// Step 3: draw the green circle at any point
ctx.globalCompositeOperation = "source-over";  // normal comp. mode
ctx.fillStyle = "#9f9";
ctx.strokeStyle = "#090";

var t = 0, dlt = 0.01;

(function loop(){
  
  ctx.clearRect(0, 0, c.width, c.height);
  t += dlt;
  
  // calc position based on t [0, 1] and the same points as for the blue
  var pos = getQuadraticPoint(radius, radius, 300, 230, c.width - radius, radius, t);
  
  // draw the arc
  ctx.beginPath();
  ctx.arc(pos.x + 2, pos.y + 2, radius, 0, 2*Math.PI);
  ctx.fill();
  
  // draw center line
  ctx.beginPath();
  ctx.moveTo(radius, radius);
  ctx.quadraticCurveTo(300, 230, c.width - radius - 6, radius);
  ctx.stroke();

  // draw blue outline on top
  ctx.drawImage(co, 0, 0);
  
  if (t <0  || t >= 1) dlt = -dlt;  // ping-pong for demo
  requestAnimationFrame(loop);
})();

// formula for quadr. curve is: B(t) = (1-t)^2 * Z0 + 2(1-t)t * C + t^2 * Z1
function getQuadraticPoint(z0x, z0y, cx, cy, z1x, z1y, t) {

  var t1 = (1 - t),       // (1 - t)
      t12 = t1 * t1,      // (1 - t) ^ 2
      t2 = t * t,         // t ^ 2
      t21tt = 2 * t1 * t; // 2(1-t)t

  return {
    x: t12 * z0x + t21tt * cx + t2 * z1x,
    y: t12 * z0y + t21tt * cy + t2 * z1y
  }
}
<canvas width=600 height=600></canvas>

按比例使用非 1:1 轴的示例:

var c = document.querySelector("canvas"),
    co = document.createElement("canvas"),
    ctx = c.getContext("2d"),
    ctxo = co.getContext("2d"),
    radius = 150,
    dia = radius * 2;

co.width = c.width;
co.height = c.height;

ctxo.translate(2,2);           // to avoid clipping of edges in this demo
ctxo.strokeStyle = "blue";
ctxo.lineWidth = dia;
ctxo.lineCap = "round";

// draw bezier (quadratic here, one control point)
ctxo.moveTo(radius, radius);
ctxo.quadraticCurveTo(300, 230, c.width - radius - 6, radius);
ctxo.stroke();

// draw multiple times to main canvas
var thickness = 2, angle = 0, step = Math.PI * 0.25;
for(; angle < Math.PI * 2; angle += step) {
  var x = thickness * Math.cos(angle),
      y = thickness * Math.sin(angle);
  ctx.drawImage(co, x, y);
}

// punch out center
ctx.globalCompositeOperation = "destination-out";
ctx.drawImage(co, 0, 0);

// back-up result by reusing off-screen canvas
ctxo.setTransform(1,0,0,1,0,0);  // remove scale
ctxo.clearRect(0, 0, co.width, co.height);
ctxo.drawImage(c, 0, 0);

// Step 3: draw the green circle at any point
ctx.globalCompositeOperation = "source-over";  // normal comp. mode
ctx.fillStyle = "#9f9";
ctx.strokeStyle = "#090";

ctx.scale(1, 0.4);            // create ellipse

var t = 0, dlt = 0.01;

(function loop(){
  
  ctx.clearRect(0, 0, c.width, c.height * 1 / 0.4);
  t += dlt;
  
  // calc position based on t [0, 1] and the same points as for the blue
  var pos = getQuadraticPoint(radius, radius, 300, 230, c.width - radius, radius, t);
  
  // draw the arc
  ctx.beginPath();
  ctx.arc(pos.x + 2, pos.y + 2, radius, 0, 2*Math.PI);
  ctx.fill();
  
  // draw center line
  ctx.beginPath();
  ctx.moveTo(radius, radius);
  ctx.quadraticCurveTo(300, 230, c.width - radius - 6, radius);
  ctx.stroke();

  // draw blue outline on top
  ctx.drawImage(co, 0, 0);
  
  if (t <0  || t >= 1) dlt = -dlt;  // ping-pong for demo
  requestAnimationFrame(loop);
})();

// formula for quadr. curve is: B(t) = (1-t)^2 * Z0 + 2(1-t)t * C + t^2 * Z1
function getQuadraticPoint(z0x, z0y, cx, cy, z1x, z1y, t) {

  var t1 = (1 - t),       // (1 - t)
      t12 = t1 * t1,      // (1 - t) ^ 2
      t2 = t * t,         // t ^ 2
      t21tt = 2 * t1 * t; // 2(1-t)t

  return {
    x: t12 * z0x + t21tt * cx + t2 * z1x,
    y: t12 * z0y + t21tt * cy + t2 * z1y
  }
}
<canvas width=600 height=600></canvas>