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的原始圆心经过变换矩阵后,就是椭圆弧的中心。步骤如下:
- 在未应用变换的坐标系下,计算arcTo的原始圆心(需要当前点
p0、控制点p1、终点p2、原始半径r)。 - 使用
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
相关产品推荐
相关产品推荐

