我正在尝试将svg路径转换为javascript中的画布,但是将svg路径椭圆弧映射到画布路径真的很难.其中一种方法是使用多个贝塞尔曲线进行近似.
我已经成功地实现了具有贝塞尔曲线的椭圆弧的近似,但是近似不是非常准确.
我的代码:
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
canvas.width = document.body.clientWidth;
canvas.height = document.body.clientHeight;
ctx.strokeWidth = 2;
ctx.strokeStyle = "#000000";
function clamp(value, min, max) {
return Math.min(Math.max(value, min), max)
}
function svgAngle(ux, uy, vx, vy ) {
var dot = ux*vx + uy*vy;
var len = Math.sqrt(ux*ux + uy*uy) * Math.sqrt(vx*vx + vy*vy);
var ang = Math.acos( clamp(dot / len,-1,1) );
if ( (ux*vy - uy*vx) < 0)
ang = -ang;
return ang;
}
function generateBezierPoints(rx, ry, phi, flagA, flagS, x1, y1, x2, y2) {
var rX = Math.abs(rx);
var rY = Math.abs(ry);
var dx2 = (x1 - x2)/2;
var dy2 = (y1 - y2)/2;
var x1p = Math.cos(phi)*dx2 + Math.sin(phi)*dy2;
var y1p = -Math.sin(phi)*dx2 + Math.cos(phi)*dy2;
var rxs = rX * rX;
var rys = rY * rY;
var x1ps = x1p * x1p;
var y1ps = y1p * y1p;
var cr = x1ps/rxs + y1ps/rys;
if (cr > 1) {
var s = Math.sqrt(cr);
rX = s * rX;
rY = s * rY;
rxs = rX * rX;
rys = rY * rY;
}
var dq = (rxs * y1ps + rys * x1ps);
var pq = (rxs*rys - dq) / dq;
var q = Math.sqrt( Math.max(0,pq) );
if (flagA === flagS)
q = -q;
var cxp = q * rX * y1p / rY;
var cyp = - q * rY * x1p / rX;
var cx = Math.cos(phi)*cxp - Math.sin(phi)*cyp + (x1 + x2)/2;
var cy = Math.sin(phi)*cxp + Math.cos(phi)*cyp + (y1 + y2)/2;
var theta = svgAngle( 1,0, (x1p-cxp) / rX, (y1p - cyp)/rY );
var delta = svgAngle(
(x1p - cxp)/rX, (y1p - cyp)/rY,
(-x1p - cxp)/rX, (-y1p-cyp)/rY);
delta = delta - Math.PI * 2 * Math.floor(delta / (Math.PI * 2));
if (!flagS)
delta -= 2 * Math.PI;
var n1 = theta, n2 = delta;
// E(n)
// cx +acos?cos??bsin?sin?
// cy +asin?cos?+bcos?sin?
function E(n) {
var enx = cx + rx * Math.cos(phi) * Math.cos(n) - ry * Math.sin(phi) * Math.sin(n);
var eny = cy + rx * Math.sin(phi) * Math.cos(n) + ry * Math.cos(phi) * Math.sin(n);
return {x: enx,y: eny};
}
// E'(n)
// ?acos?sin??bsin?cos?
// ?asin?sin?+bcos?cos?
function Ed(n) {
var ednx = -1 * rx * Math.cos(phi) * Math.sin(n) - ry * Math.sin(phi) * Math.cos(n);
var edny = -1 * rx * Math.sin(phi) * Math.sin(n) + ry * Math.cos(phi) * Math.cos(n);
return {x: ednx, y: edny};
}
var n = [];
n.push(n1);
var interval = Math.PI/4;
while(n[n.length - 1] + interval < n2)
n.push(n[n.length - 1] + interval)
n.push(n2);
function getCP(n1, n2) {
var en1 = E(n1);
var en2 = E(n2);
var edn1 = Ed(n1);
var edn2 = Ed(n2);
var alpha = Math.sin(n2 - n1) * (Math.sqrt(4 + 3 * Math.pow(Math.tan((n2 - n1)/2), 2)) - 1)/3;
console.log(en1, en2);
return {
cpx1: en1.x + alpha*edn1.x,
cpy1: en1.y + alpha*edn1.y,
cpx2: en2.x - alpha*edn2.x,
cpy2: en2.y - alpha*edn2.y,
en1: en1,
en2: en2
};
}
var cps = []
for(var i = 0; i < n.length - 1; i++) {
cps.push(getCP(n[i],n[i+1]));
}
return cps;
}
// M100,200
ctx.moveTo(100,200)
// a25,100 -30 0,1 50,-25
var rx = 25, ry=100 ,phi = -30 * Math.PI / 180, fa = 0, fs = 1, x = 100, y = 200, x1 = x + 50, y1 = y - 25;
var cps = generateBezierPoints(rx, ry, phi, fa, fs, x, y, x1, y1);
var limit = 4;
for(var i = 0; i < limit && i < cps.length; i++) {
ctx.bezierCurveTo(cps[i].cpx1, cps[i].cpy1,
cps[i].cpx2, cps[i].cpy2,
i < limit - 1 ? cps[i].en2.x : x1, i < limit - 1 ? cps[i].en2.y : y1);
}
ctx.stroke()
Run Code Online (Sandbox Code Playgroud)
结果如下:
红线表示svg路径椭圆弧,黑线表示近似值
如何在画布上准确绘制任何可能的椭圆弧?
忘了提到算法的原始来源:https://mortoray.com/2017/02/16/rendering-an-svg-elliptical-arc-as-bezier-curves/
所以这两个错误很简单:
\n\nn2 = theta + delta;rX rY而不是rx ry.这解决了一切。尽管原始版本显然应该选择将弧划分为相同大小的部分,而不是 pi/4 大小的元素,然后附加其余部分。只需找出需要多少个部分,然后将范围划分为多个相同大小的部分,这似乎是一个更优雅的解决方案,并且由于误差随着长度的增加而增加,因此也会更准确。
\n\n请参阅: https: //jsfiddle.net/Tatarize/4ro0Lm4u/了解工作版本。
\n\n它不仅在这一方面存在问题,而且在大多数地方都不起作用。你可以看到,根据 phi,它会做很多不同的坏事。那里实际上好得令人震惊。但是,其他地方也坏了。
\n\nhttps://jsfiddle.net/Tatarize/dm7yqypb/
\n\n原因是n2的声明是错误的,应该是:
\n\nn2 = theta + delta;\nRun Code Online (Sandbox Code Playgroud)\n\nhttps://jsfiddle.net/Tatarize/ba903pss/ \n但是,修复了索引中的错误,它显然没有像应有的那样扩展。svg 标准中的弧可能会被放大,这样肯定会有一个解决方案,而在相关代码中,它们看起来像是被夹紧的。
\n\nhttps://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
\n\n\n\n\n“如果 rx、ry 和 \xcf\x86 没有解(基本上,\n 椭圆不够大,无法从 (x1, y1) 到达 (x2, y2)),则\n 椭圆将被缩放均匀地向上直到恰好有一个解\n(直到椭圆足够大)。”
\n
对此进行测试,因为它确实具有应该扩展它的代码,所以当该代码被调用时我将其更改为绿色。当它搞砸时它会变成绿色。是的,由于某种原因它无法扩展:
\n\nhttps://jsfiddle.net/Tatarize/tptroxho/
\n\n这意味着某些东西正在使用 rx 而不是缩放的 rX,它是 E 和 Ed 函数:
\n\nvar enx = cx + rx * Math.cos(phi) * Math.cos(n) - ry * Math.sin(phi) * Math.sin(n);\nRun Code Online (Sandbox Code Playgroud)\n\n这些rx参考文献必须阅读rX和。rYry
var enx = cx + rX * Math.cos(phi) * Math.cos(n) - rY * Math.sin(phi) * Math.sin(n);\nRun Code Online (Sandbox Code Playgroud)\n\n最终修复了最后一个错误,QED。
\n\nhttps://jsfiddle.net/Tatarize/4ro0Lm4u/
\n\n我摆脱了画布,将所有内容移动到 svg 并对其进行动画处理。
\n\nvar svgNS = "http://www.w3.org/2000/svg";\nvar svg = document.getElementById("svg");\nvar arcgroup = document.getElementById("arcgroup");\nvar curvegroup = document.getElementById("curvegroup");\n\nfunction doArc() {\n while (arcgroup.firstChild) {\n arcgroup.removeChild(arcgroup.firstChild);\n } //clear old svg data. -->\n var d = document.createElementNS(svgNS, "path");\n //var path = "M100,200 a25,100 -30 0,1 50,-25"\n var path = "M" + x + "," + y + "a" + rx + " " + ry + " " + phi + " " + fa + " " + fs + " " + " " + x1 + " " + y1;\n d.setAttributeNS(null, "d", path);\n arcgroup.appendChild(d);\n}\n\nfunction doCurve() {\n var cps = generateBezierPoints(rx, ry, phi * Math.PI / 180, fa, fs, x, y, x + x1, y + y1);\n\n while (curvegroup.firstChild) {\n curvegroup.removeChild(curvegroup.firstChild);\n } //clear old svg data. -->\n var d = document.createElementNS(svgNS, "path");\n var limit = 4;\n var path = "M" + x + "," + y;\n for (var i = 0; i < limit && i < cps.length; i++) {\n if (i < limit - 1) {\n path += "C" + cps[i].cpx1 + " " + cps[i].cpy1 + " " + cps[i].cpx2 + " " + cps[i].cpy2 + " " + cps[i].en2.x + " " + cps[i].en2.y;\n } else {\n path += "C" + cps[i].cpx1 + " " + cps[i].cpy1 + " " + cps[i].cpx2 + " " + cps[i].cpy2 + " " + (x + x1) + " " + (y + y1);\n }\n }\n d.setAttributeNS(null, "d", path);\n d.setAttributeNS(null, "stroke", "#000");\n curvegroup.appendChild(d);\n}\n\nsetInterval(phiClock, 50);\n\nfunction phiClock() {\n phi += 1;\n doCurve();\n doArc();\n}\ndoCurve();\ndoArc();\nRun Code Online (Sandbox Code Playgroud)\n
| 归档时间: |
|
| 查看次数: |
85 次 |
| 最近记录: |