将 SVG ARCTO 映射到 HTML Canvas ARCTO

App*_*rew 4 svg canvas html5-canvas

SVG 规范中的 ARCTO与我们在Canvas中的规范有很大不同。我有一个用例,我将根据 SVG 规范获取数据,但我需要在 Canvas 上绘制它。

我尝试过这个,但我猜我的几何学很弱。你能帮忙吗?

mii*_*le7 5

仅绘图(更新)

\n

正如@V.Rubinetti所说,canvas\'Path2D可以处理 SVG 路径字符串。因此,如果您只关心使用 javascript 进行绘图,您可以从那里使用圆弧,例如

\n
const ctx = canvas.getContext("2d");\nlet p = new Path2D("M10 10 h 80 v 80 h -80 Z");\nctx.fill(p);\n
Run Code Online (Sandbox Code Playgroud)\n

如果您像我一样需要转换实际的弧线,或者也像我一样实际上不使用 javascript,请使用下面我的最初答案。

\n

语言不可知论(初始答案)

\n

有关交互式示例,请查看下面的代码片段。

\n

我遇到了同样的问题,所以我看到了这篇文章。W3C SVG 定义的实现要求附录准确地说明了如何将形式(他们称之为)端点参数化转换为中心参数化并返回:

\n

SVG 弧(端点参数化)描述如下:

\n
    \n
  • x 1 /y 1 : 起始位置(最后一条路径命令的位置)
  • \n
  • x 2 /y 2:圆弧的结束位置(该路径命令的x和值)y
  • \n
  • r x /r y:x 和 y 半径
  • \n
  • \xcf\x86:旋转角度
  • \n
  • f A:大弧标志(1或0,是否使用大弧或小弧)
  • \n
  • f S:扫描标志(顺时针还是逆时针)
  • \n
\n

画布圆弧使用(中心点参数化):

\n
    \n
  • c x /c y : 椭圆的中心点
  • \n
  • r x /r y:x 和 y 半径
  • \n
  • \xcf\x86:旋转角度
  • \n
  • \xce\xb8 1 : 椭圆的起始角度(旋转前)
  • \n
  • \xce\x94\xce\xb8:椭圆使用的角度距离(方向取决于扫描标志 f S,您还可以计算终点 \xce\xb8 2可能更好)
  • \n
\n

从 SVG 转换为 Canvas

\n

这意味着从 SVG 转换为画布,您可以使用以下方程式(直接取自 W3C 的给定 url):

\n
\n
    \n
  1. 计算(x1\xe2\x80\xb2, y1\xe2\x80\xb2)(公式 F.6.5.1)

    \n

    公式 F.6.5.1

    \n
  2. \n
  3. 计算(cx\xe2\x80\xb2, cy\xe2\x80\xb2)(公式 F.6.5.2)

    \n

    公式 F.6.5.2

    \n

    其中,如果 f A \xe2\x89\xa0 f S则选择 + 符号,如果 f A = f S则选择 \xe2\x88\x92\n 符号。

    \n
  4. \n
  5. (cx, cy)根据(cx\xe2\x80\xb2, cy\xe2\x80\xb2)(方程 F.6.5.3)计算

    \n

    公式 F.6.5.3

    \n
  6. \n
  7. 计算 \xce\xb8 1和 \xce\x94\xce\xb8 (方程 F.6.5.5 和 F.6.5.6)

    \n

    编辑:我现在正在使用其他方程,看看底部

    \n

    公式 F.6.5.5

    \n

    公式 F.6.5.6

    \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
  8. \n
\n
\n

版权所有 \xc2\xa9 2011 年 8 月 16 日万维网联盟(MIT、ERCIM、Keio、Beihang)。http://www.w3.org/Consortium/Legal/2015/doc-license

\n

编辑:修改步骤 4 的方程。

\n

我现在使用以下方程来确定 \xce\xb8 1和 \xce\x94\xce\xb8:

\n

修正方程F.6.5.5

\n

修改后的方程 F.6.5.6

\n

这只是圆弧的起点和终点与中心点之间的矢量。由于角度是在旋转之前计算的,因此要减去\xcf\x86。如果需要,您可以将其保留。

\n

我收到了给定方程的错误结果,但这也可能是我的实现中的一个错误。当试图找到这个 bug 时,我想到了 W3C 在这里所做的事情。我正在研究如何计算角度,这是我想到的第一件事。这对我来说是正确的结果。

\n

实施示例

\n

\r\n
\r\n
function 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
\r\n
\r\n

\n

从画布转换为 SVG

\n

在转换回来时使用 W3C 方程时我也遇到了问题。这可能是因为改变了角度。为了从 Canvas 转换为 SVG,您需要将起始角度和结束角度 (\xce\xb8 1和 \xce\xb8 2 = \xce\xb8 1 + \xce\x94\xce\xb8) 以及中心点一起转换为圆弧的交点。这些是 SVG 弧的起点和终点。

\n
    \n
  1. 计算(x1\', y1\')(x2\', y2\')

    \n

    计算 x1\'、y1\'、x2\' 和 y2\'

    \n

    这是计算旋转坐标系中给定角度 \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
  2. \n
  3. 计算(x1, y1)(x2, y2)

    \n

    计算 x1、y1、x2 和 y2

    \n

    然后可以通过向后旋转旋转角度 \xcf\x86 并将矢量平移到椭圆的中心来计算起点和终点的 x 和 y 坐标。

    \n
  4. \n
  5. 找到标志

    \n

    标志很容易确定:如果 \xce\x94\xce\xb8 大于 180\xc2\xb0,则 f A 为 1;如果 \xce\x94\xce\xb8 大于 0\xc2\xb0,则 fS 为 1

    \n
  6. \n
\n