You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Canvas中arcTo绘制圆弧的圆心计算及变换后半径求解

Canvas变换后arcTo圆弧的半径与圆心计算方案

你的代码问题

你当前使用的getActualRadius函数公式存在错误:

  • 在均匀缩放+旋转的场景下,该公式会得出s²*r/|cosθ|的结果,但实际正确的变换后半径应为s*r(s为均匀缩放因子),明显不符。
  • 当变换包含错切(transform.b或transform.c不为0)时,原始圆弧会被拉伸为椭圆弧,此时单一“半径”的概念不再成立,需要计算椭圆的两个半轴长。

正确解决方案

1. 计算变换后椭圆弧的半轴长

Canvas的仿射变换线性部分(矩阵[[a,c],[b,d]])的奇异值,乘以原始半径r就是椭圆的长、短半轴长。代码实现:

function getEllipseSemiAxes(ctx, radius) {
  const { a, b, c, d } = ctx.getTransform();
  // 计算矩阵 M^T * M 的元素
  const mtm11 = a*a + b*b;
  const mtm12 = a*c + b*d;
  const mtm22 = c*c + d*d;
  // 求解特征值
  const trace = mtm11 + mtm22;
  const det = mtm11*mtm22 - mtm12*mtm12;
  const sqrtDiscriminant = Math.sqrt(trace*trace - 4*det);
  const lambda1 = (trace + sqrtDiscriminant) / 2;
  const lambda2 = (trace - sqrtDiscriminant) / 2;
  // 奇异值为特征值的平方根,乘以原始半径得到半轴长
  return [Math.sqrt(lambda1)*radius, Math.sqrt(lambda2)*radius];
}

调用后返回的两个值,分别对应椭圆的长半轴和短半轴。

2. 计算变换后椭圆弧的中心

arcTo的原始圆心经过变换矩阵后,就是椭圆弧的中心。步骤如下:

  1. 在未应用变换的坐标系下,计算arcTo的原始圆心(需要当前点p0、控制点p1、终点p2、原始半径r)。
  2. 使用ctx.getTransform().transformPoint()方法,将原始圆心转换到当前变换后的坐标系。

计算原始圆心的代码:

function getArcToCenter(p0, p1, p2, r) {
  // 计算两条切线的方向向量
  const v1 = { x: p1.x - p0.x, y: p1.y - p0.y };
  const v2 = { x: p2.x - p1.x, y: p2.y - p1.y };
  
  // 归一化方向向量
  const len1 = Math.hypot(v1.x, v1.y);
  const len2 = Math.hypot(v2.x, v2.y);
  const u1 = { x: v1.x/len1, y: v1.y/len1 };
  const u2 = { x: v2.x/len2, y: v2.y/len2 };
  
  // 计算角平分线方向
  const bisector = { x: u1.x + u2.x, y: u1.y + u2.y };
  const bisectorLen = Math.hypot(bisector.x, bisector.y);
  const uBisector = { x: bisector.x/bisectorLen, y: bisector.y/bisectorLen };
  
  // 计算从控制点p1到圆心的距离
  const dot = u1.x*u2.x + u1.y*u2.y;
  const theta = Math.acos(Math.max(-1, Math.min(1, dot))); // 避免数值溢出
  const dist = r / Math.sin(theta/2);
  
  // 判断圆心方向(对应arcTo的默认内侧圆弧)
  const cross = u1.x*u2.y - u1.y*u2.x;
  const direction = cross < 0 ? -1 : 1;
  
  // 返回原始圆心坐标
  return {
    x: p1.x + direction * uBisector.x * dist,
    y: p1.y + direction * uBisector.y * dist
  };
}

变换圆心的调用示例:

// 假设已知当前点p0、控制点p1、终点p2、原始半径r
const originalCenter = getArcToCenter(p0, p1, p2, r);
const transform = ctx.getTransform();
const transformedCenter = transform.transformPoint(originalCenter);

总结

  • 若变换包含错切,原始圆弧会转为椭圆弧,需计算两个半轴长而非单一半径。
  • 变换后的圆心可通过“原始圆心经变换矩阵转换”直接得到。
  • 你提供的原始公式仅在无旋转的均匀缩放场景下可能巧合正确,不具备通用性。

内容的提问来源于stack exchange,提问作者laohaiyi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 18:15:26