App*_*rew 4 svg canvas html5-canvas
SVG 规范中的 ARCTO与我们在Canvas中的规范有很大不同。我有一个用例,我将根据 SVG 规范获取数据,但我需要在 Canvas 上绘制它。
我尝试过这个,但我猜我的几何学很弱。你能帮忙吗?
正如@V.Rubinetti所说,canvas\'Path2D可以处理 SVG 路径字符串。因此,如果您只关心使用 javascript 进行绘图,您可以从那里使用圆弧,例如
const ctx = canvas.getContext("2d");\nlet p = new Path2D("M10 10 h 80 v 80 h -80 Z");\nctx.fill(p);\nRun Code Online (Sandbox Code Playgroud)\n如果您像我一样需要转换实际的弧线,或者也像我一样实际上不使用 javascript,请使用下面我的最初答案。
\n有关交互式示例,请查看下面的代码片段。
\n我遇到了同样的问题,所以我看到了这篇文章。W3C SVG 定义的实现要求附录准确地说明了如何将形式(他们称之为)端点参数化转换为中心参数化并返回:
\nSVG 弧(端点参数化)描述如下:
\nx和值)y画布圆弧使用(中心点参数化):
\n这意味着从 SVG 转换为画布,您可以使用以下方程式(直接取自 W3C 的给定 url):
\n\n\n\n
\n- \n
计算
\n\n(x1\xe2\x80\xb2, y1\xe2\x80\xb2)(公式 F.6.5.1)- \n
计算
\n\n(cx\xe2\x80\xb2, cy\xe2\x80\xb2)(公式 F.6.5.2)其中,如果 f A \xe2\x89\xa0 f S则选择 + 符号,如果 f A = f S则选择 \xe2\x88\x92\n 符号。
\n- \n
\n\n
(cx, cy)根据(cx\xe2\x80\xb2, cy\xe2\x80\xb2)(方程 F.6.5.3)计算- \n
计算 \xce\xb8 1和 \xce\x94\xce\xb8 (方程 F.6.5.5 和 F.6.5.6)
\n编辑:我现在正在使用其他方程,看看底部
\n\n\n其中 \xce\xb8 1固定在 \xe2\x88\x92360\xc2\xb0 < \xce\x94\xce\xb8 < 360\xc2\xb0 范围内,这样:
\n如果 f S = 0,则 \xce\x94\xce\xb8 < 0,
\n否则如果 f S = 1,则 \xce\x94\xce\xb8 > 0。
\n换句话说,如果 f S = 0 并且 (F.6.5.6) 的右侧大于 0,则减去 360\xc2\xb0,而如果 f S = 1\n并且 (F.6.5.6) 的右侧大于 0,则减去 360\xc2\xb0。 6.5.6) 小于0,则加上360\xc2\xb0。在所有情况下,\n请保持原样。
\n
版权所有 \xc2\xa9 2011 年 8 月 16 日万维网联盟(MIT、ERCIM、Keio、Beihang)。http://www.w3.org/Consortium/Legal/2015/doc-license
\n我现在使用以下方程来确定 \xce\xb8 1和 \xce\x94\xce\xb8:
\n\n\n这只是圆弧的起点和终点与中心点之间的矢量。由于角度是在旋转之前计算的,因此要减去\xcf\x86。如果需要,您可以将其保留。
\n我收到了给定方程的错误结果,但这也可能是我的实现中的一个错误。当试图找到这个 bug 时,我想到了 W3C 在这里所做的事情。我正在研究如何计算角度,这是我想到的第一件事。这对我来说是正确的结果。
\nfunction convertSVGToCanvas(x1, y1, x2, y2, rx, ry, phi, fa, fs) {\n if(rx <= 0 || ry <= 0) {\n throw new Exception("rx or ry is <= 0");\n }\n \n const p = phi / 180 * Math.PI;\n const x_m = (x1 - x2) / 2;\n const y_m = (y1 - y2) / 2;\n \n const x1_d = Math.cos(p) * x_m + Math.sin(p) * y_m;\n const y1_d = -Math.sin(p) * x_m + Math.cos(p) * y_m;\n \n const radius_check_value = (x1_d*x1_d)/(rx*rx) + (y1_d*y1_d)/(ry*ry);\n if(radius_check_value > 1) {\n // throw "Radius is too small to build an arc!";\n \n // Check out radius correction in the W3C document\n // https://www.w3.org/TR/SVG11/implnote.html#ArcCorrectionOutOfRangeRadii\n const r_sq = Math.sqrt(radius_check_value);\n rx = r_sq * rx;\n ry = r_sq * ry;\n \n console.error(`Radii are too small to build an arc. Correcting them to ${rx}/${ry}.`);\n }\n \n const sq = Math.sqrt(\n (rx*rx*ry*ry - rx*rx*y1_d*y1_d - ry*ry*x1_d*x1_d) / \n (rx*rx*y1_d*y1_d + ry*ry*x1_d*x1_d)\n );\n \n const s = fa != fs ? 1 : -1;\n const cx_d = s * sq * rx*y1_d/ry;\n const cy_d = s * sq * -ry*x1_d/rx;\n \n const x_m_d = (x1 + x2) / 2;\n const y_m_d = (y1 + y2) / 2;\n const cx = Math.cos(p) * cx_d - Math.sin(p) * cy_d + x_m_d;\n const cy = Math.sin(p) * cx_d + Math.cos(p) * cy_d + y_m_d;\n \n const vectorAngle = (ux, uy, vx, vy) => (\n (ux*vy >= uy*vx ? 1 : -1) *\n Math.acos(\n (ux*vx + uy*vy) / \n (Math.sqrt(ux*ux + uy*uy) * Math.sqrt(vx*vx + vy*vy))\n )\n );\n const theta_1 = vectorAngle(1, 0, x1 - cx, y1 - cy) - p;\n const delta_theta = vectorAngle(x1 - cx, y1 - cy, x2 - cx, y2 - cy);\n \n return [cx, cy, rx, ry, p, theta_1, theta_1 + delta_theta];\n}\n\n// for example only\n\nfunction getInputs() {\n const inputs = ["x1", "y1", "x2", "y2", "rx", "ry", "phi", "fa", "fs"];\n return inputs.map((id) => (\n id == "fa" || id == "fs" \n ? $(`#${id}`).prop("checked")\n : parseFloat($(`#${id}`).val())\n ));\n}\n\nfunction updateDrawing() {\n const [x1, y1, x2, y2, rx, ry, phi, fa, fs] = getInputs();\n \n // draw svg\n $("#path").attr("d", `M ${x1} ${y1} A ${rx} ${ry} ${phi} ${fa ? 1 : 0} ${fs ? 1 : 0} ${x2} ${y2}`);\n \n // draw canvas\n const canvas = $("#canvas")[0];\n const ctx = canvas.getContext("2d");\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n ctx.fillStyle = "red";\n ctx.beginPath();\n ctx.ellipse(0, 0, 5, 5, 0, 0, 2 * Math.PI);\n ctx.fill();\n ctx.beginPath();\n ctx.ellipse(canvas.width, 0, 5, 5, 0, 0, 2 * Math.PI);\n ctx.fill();\n ctx.beginPath();\n ctx.ellipse(0, canvas.height, 5, 5, 0, 0, 2 * Math.PI);\n ctx.fill();\n ctx.beginPath();\n ctx.ellipse(canvas.width, canvas.height, 5, 5, 0, 0, 2 * Math.PI);\n ctx.fill();\n \n ctx.beginPath();\n ctx.fillStyle = "transparent";\n const [cx, cy, canv_rx, canv_ry, canv_phi, theta_1, delta_theta] = convertSVGToCanvas(x1, y1, x2, y2, rx, ry, phi, fa, fs);\n \n ctx.ellipse(cx, cy, canv_rx, canv_ry, canv_phi, theta_1, delta_theta, !fs);\n ctx.stroke();\n\n};\n\n$(document).ready(() => {\n $("input").on("change", updateDrawing);\n updateDrawing();\n});Run Code Online (Sandbox Code Playgroud)\r\n.wrapper {\n font-family: sans-serif;\n display: grid;\n grid-template-columns: 150px 150px auto;\n align-items: start;\n}\n\nsvg, canvas {\n border: 1px dashed #ccc;\n}\n\n.controls {\n display: grid;\n grid-template-columns: repeat(4, 1fr);\n align-items: center;\n}\n\n.controls label[for="phi"]{\n grid-column: 2 / 5;\n}\n\nh1 {\n margin: 3px;\n font-size: 120%;\n}\n\ninput {\n width: 50px;\n}Run Code Online (Sandbox Code Playgroud)\r\n<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>\n<div class="wrapper">\n <h1>SVG</h1>\n <h1>Canvas</h1>\n <h1>Controls</h1>\n <svg id="svg" width="150" height="150">\n <circle cx="0" cy="0" r="5" fill="red" />\n <circle cx="150" cy="0" r="5" fill="red" />\n <circle cx="0" cy="150" r="5" fill="red" />\n <circle cx="150" cy="150" r="5" fill="red" />\n <path id="path" d="" stroke="green" fill="none" stroke-width="2" />\n </svg>\n <canvas id="canvas" width="150" height="150"></canvas>\n <div class="controls">\n <input id="x1" type="number" value="40" />\n <label for="x1">x1</label>\n <input id="y1" type="number" value="60" />\n <label for="y1">y1</label>\n \n <input id="x2" type="number" value="40" />\n <label for="x2">x2</label>\n <input id="y2" type="number" value="140" />\n <label for="y2">y2</label>\n \n <input id="rx" type="number" value="40" />\n <label for="rx">rx</label>\n <input id="ry" type="number" value="100" />\n <label for="ry">ry</label>\n \n <input id="phi" type="number" value="30" />\n <label for="phi">\xcf\x86 (in deg)</label>\n \n <input id="fa" type="checkbox" />\n <label for="fa">f<sub>A</sub></label>\n <input id="fs" type="checkbox" checked />\n <label for="fs">f<sub>S</sub></label>\n </div>\n</div>Run Code Online (Sandbox Code Playgroud)\r\n在转换回来时使用 W3C 方程时我也遇到了问题。这可能是因为改变了角度。为了从 Canvas 转换为 SVG,您需要将起始角度和结束角度 (\xce\xb8 1和 \xce\xb8 2 = \xce\xb8 1 + \xce\x94\xce\xb8) 以及中心点一起转换为圆弧的交点。这些是 SVG 弧的起点和终点。
\n计算(x1\', y1\')和(x2\', y2\')
这是计算旋转坐标系中给定角度 \xce\xb8 1 /\xce\xb8 2定义的线的交点。对于 x 坐标,应选择 + 号,当 -\xcf\x80/2 \xe2\x89\xa4 \xce\xb8 \xe2\x89\xa4 \xcf\x80/2 时。当 0 \xe2\x89\xa4 \xce\xb8 \xe2\x89\xa4 \xcf\x80 时,应选择 y 坐标的 + 号。
\n计算(x1, y1)和(x2, y2)
然后可以通过向后旋转旋转角度 \xcf\x86 并将矢量平移到椭圆的中心来计算起点和终点的 x 和 y 坐标。
\n找到标志
\n标志很容易确定:如果 \xce\x94\xce\xb8 大于 180\xc2\xb0,则 f A 为 1;如果 \xce\x94\xce\xb8 大于 0\xc2\xb0,则 fS 为 1 。
\n