4

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

我试过这个,但我想我的几何形状很弱。你能帮忙吗?

4

5 回答 5

4

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

SVG 弧(端点参数化)由以下内容描述:

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

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

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

从 SVG 转换为画布

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

  1. 计算(x1′, y1′)(公式 F.6.5.1)

    公式 F.6.5.1

  2. 计算(cx′, cy′)(公式 F.6.5.2)

    公式 F.6.5.2

    如果 f A ≠ f S则选择 + 号,如果 f A = f S则选择 - 号。

  3. (cx, cy)根据(cx′, cy′)(公式 F.6.5.3)计算

    公式 F.6.5.3

  4. 计算 θ 1和 Δθ(公式 F.6.5.5 和 F.6.5.6)

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

    公式 F.6.5.5

    公式 F.6.5.6

    其中 θ 1固定在 -360° < Δθ < 360° 的范围内,因此:

    如果 f S = 0,则 Δθ < 0,

    否则,如果 f S = 1,则 Δθ > 0。

    换句话说,如果 f S = 0 并且 (F.6.5.6) 的右侧大于 0,则减去 360°,而如果 f S = 1 并且 (F.6.5.6) 的右侧是小于 0,则加 360°。在所有其他情况下,保持原样。

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

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

我现在使用以下等式来确定 θ 1和 Δθ:

修正方程 F.6.5.5

修正公式 F.6.5.6

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

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

从画布转换为 SVG

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

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

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

    这是计算由旋转坐标系中给定角度 θ 12定义的线的交点。对于 x 坐标,当 -π/2 ≤ θ ≤ π/2 时,应选择 + 号。当 0 ≤ θ ≤ π 时,应选择 y 坐标的 + 号。

  2. 计算(x1, y1)(x2, y2)

    计算 x1、y1、x2 和 y2

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

  3. 找到旗帜

    标志很容易确定:如果 Δθ 大于 180°,则 f A为 1,如果 Δθ 大于 0°,则 fS 为 1。

于 2019-03-22T08:48:03.290 回答
3

svg ellipse 和canvas arc 的区别在于svg 中有2 个半径,而arcTo 中只有一个。然后你还需要在画布上以特定角度旋转你的弧线。要模拟 2 个半径,您需要使用具有最小半径的给定坐标创建一个弧。然后,您需要使用系数(rx/ry)在特定方向上缩放此弧。现在你只需要旋转。但是在这种方法中,很难确定要显示椭圆的哪一部分,因为它取决于 svg 规范中的大弧标志和扫描标志。另一个问题是通过结束坐标(来自 svg 规范)限制你的弧。所以我猜,通过 arcTo,你最多可以构建一个椭圆的一半。

如果椭圆上有 3 个控制点的坐标,您也可以使用 bezierCurveTo(x0,y0,x1,y1,x2,y2) 绘制椭圆的一部分。使用这种方法,您可以构建椭圆的任何部分。当然,对于超过 PI 的段,您至少需要两条曲线

根据您拥有的 SVG 规范(rx ry x-axis-rotation large-arc-flag sweep-flag xy)。所以示例路径将是这样的:

  M100,100 a25,50 -30 0,1 50,-25

在这里,您可能会发现应该如何绘制贝塞尔曲线。

现在您有一个上下文点(即 100,100)和一个终点(即 100+50,100-25)您需要在旋转到 -30 度之前计算控制点。

这是一个对我有用的例子:

$(document).ready(function(){
        var startX = 100;
        var startY = 100;
        var dX = 50;
        var dY = -25;
        var angle = -30;
        var rx = 25;
        var ry = 50;
        var svg = Raphael($('#svg')[0], 200, 200);

        var path = "M" +startX + "," + startY + " a" + rx + "," + ry + " " + angle + " 0,1" + " " + dX + "," +dY;
        svg.path(path).attr({"stroke-width" : 2, "stroke" : "#FFFFFF"});

        var kappa = .5522848,
        ox = rx*kappa,
        oy = ry*kappa,
        xm = startX + rx,       // x-middle
        ym = startY + ry;       // y-middle
        var canvas = document.getElementById("canvas");
        var ctx = canvas.getContext("2d");
        ctx.moveTo(startX,startY);
        ctx.bezierCurveTo(startX, startY - oy, startX + ox, startY - ry, startX + rx, startY - ry);
        ctx.bezierCurveTo(startX + rx + ox, startY - ry, startX + 2*rx, startY - oy, startX + dX, startY + dY);
        ctx.stroke();
    });

标记很简单:

<div id="svg" style="border: 1px solid black;position : absolute;top : 50px;left : 50px;"></div>
<canvas id="canvas" width="200px" height="200px" style="border: 1px solid black;position : absolute;top : 300px;left : 50px;"></canvas>

曲线不相似,因为我没有将控制点旋转到 -30 度。但我相信这是你唯一需要做的事情。因为如果您将角度 = 0。它们将是相似的您可以使用这篇文章来获得旋转的数学。

PS:我从这个答案中提取了部分代码

于 2011-07-18T06:03:01.227 回答
2

以下代码段摘自 Gabe Lerner 的综合 CANVG 包(请参阅https://github.com/canvg/canvg)的相关部分,供任何像我一样可能不想要整个九码 Gabe 的人使用包裹。与早期的解决方案不同,它不是近似值,它完全等同于 SVG 弧路径元素,我非常感谢 Gabe。

还有一点是,如果您在绘制路径之前已经对画布应用了一些缩放和/或平移,则需要将其纳入对 Context.translate 的两次调用的参数以及半径参数中对 Context.arc 的调用

function drawSVGarcOnCanvas (Context,lastX,lastY,rx,ry,xAxisRotation,largeArcFlag,sweepFlag,x,y)
{
    //--------------------
    // rx, ry, xAxisRotation, largeArcFlag, sweepFlag, x, y
    // are the 6 data items in the SVG path declaration following the A
    //
    // lastX and lastY are the previous point on the path before the arc
    //--------------------
    // useful functions
    var m   = function (   v) {return Math.sqrt (Math.pow (v[0],2) + Math.pow (v[1],2))};
    var r   = function (u, v) {return ( u[0]*v[0] + u[1]*v[1]) / (m(u) * m(v))};
    var ang = function (u, v) {return ((u[0]*v[1] < u[1]*v[0])? -1 : 1) * Math.acos (r (u,v))};
    //--------------------

var currpX =  Math.cos (xAxisRotation) * (lastX - x) / 2.0 + Math.sin (xAxisRotation) * (lastY - y) / 2.0 ;
var currpY = -Math.sin (xAxisRotation) * (lastX - x) / 2.0 + Math.cos (xAxisRotation) * (lastY - y) / 2.0 ;

var l = Math.pow (currpX,2) / Math.pow (rx,2) + Math.pow (currpY,2) / Math.pow (ry,2);
if (l > 1) {rx *= Math.sqrt (l); ry *= Math.sqrt (l)};
var s = ((largeArcFlag == sweepFlag)? -1 : 1) * Math.sqrt 
      (( (Math.pow (rx,2) * Math.pow (ry    ,2)) - (Math.pow (rx,2) * Math.pow (currpY,2)) - (Math.pow (ry,2) * Math.pow (currpX,2))) 
       / (Math.pow (rx,2) * Math.pow (currpY,2) +   Math.pow (ry,2) * Math.pow (currpX,2)));
if (isNaN (s)) s = 0 ;

var cppX = s *  rx * currpY / ry ;
var cppY = s * -ry * currpX / rx ;
var centpX = (lastX + x) / 2.0 + Math.cos (xAxisRotation) * cppX - Math.sin (xAxisRotation) * cppY ;
var centpY = (lastY + y) / 2.0 + Math.sin (xAxisRotation) * cppX + Math.cos (xAxisRotation) * cppY ;

var ang1 = ang ([1,0], [(currpX-cppX)/rx,(currpY-cppY)/ry]);
var a = [(  currpX-cppX)/rx,(currpY-cppY)/ry];
var b = [(-currpX-cppX)/rx,(-currpY-cppY)/ry];
var angd = ang (a,b);
if (r (a,b) <= -1) angd = Math.PI;
if (r (a,b) >=  1) angd = 0;

var rad = (rx > ry)? rx : ry;
var sx  = (rx > ry)? 1 : rx / ry;
var sy  = (rx > ry)? ry / rx : 1;

Context.translate (centpX,centpY);
Context.rotate (xAxisRotation);
Context.scale (sx, sy);
Context.arc (0, 0, rad, ang1, ang1 + angd, 1 - sweepFlag);
Context.scale (1/sx, 1/sy);
Context.rotate (-xAxisRotation);
Context.translate (-centpX, -centpY);
};      
于 2017-11-26T08:39:03.523 回答
1

尝试将“M100,100 a25,50 -30 0,1 50,-25”映射到画布时,请使用我的功能。当然,我写这篇文章时考虑到了圆弧。

椭圆(100,100,50,-25,50,假);

function ellipse(x1, y1, x2, y2, radius, clockwise) {

var cBx = (x1 + x2) / 2;    //get point between xy1 and xy2
var cBy = (y1 + y2) / 2;
var aB = Math.atan2(y1 - y2, x1 - x2);  //get angle to bulge point in radians
if (clockwise) { aB += (90 * (Math.PI / 180)); }
else { aB -= (90 * (Math.PI / 180)); }
var op_side = Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2)) / 2;
var adj_side = Math.sqrt(Math.pow(radius, 2) - Math.pow(op_side, 2));

if (isNaN(adj_side)) {
    adj_side = Math.sqrt(Math.pow(op_side, 2) - Math.pow(radius, 2));
}

var Cx = cBx + (adj_side * Math.cos(aB));            
var Cy = cBy + (adj_side * Math.sin(aB));
var startA = Math.atan2(y1 - Cy, x1 - Cx);       //get start/end angles in radians
var endA = Math.atan2(y2 - Cy, x2 - Cx);
var mid = (startA + endA) / 2;
var Mx = Cx + (radius * Math.cos(mid));
var My = Cy + (radius * Math.sin(mid));
context.arc(Cx, Cy, radius, startA, endA, clockwise);
}
于 2013-03-15T04:02:19.700 回答
1

有点晚了,但这是一个允许绘制楔形的解决方案,如饼图。该解决方案使用来自该线程的所有输入。所以我想我会分享。请注意,它不执行半径检查....

依赖项是mathjsd3Shape

function toContext(x1, y1, path) {
  let fvals = path.split(',').map(item => parseFloat(item)); 
  const rx = fvals[0];
  const ry = fvals[1];
  const phi = toRad(fvals[2]);
  const fA = fvals[3];
  const fS = fvals[4];
  const x2 = fvals[5];
  const y2 = fvals[6];

  const cosPhi = Math.cos(phi);
  const sinPhi = Math.sin(phi);
  const dx = (x1 - x2) / 2.0;
  const dy = (y1 - y2) / 2.0;

  let A = math.matrix([[cosPhi, sinPhi], [-sinPhi, cosPhi]]);
  let B = math.matrix([[dx], [dy]]);
  let C = math.multiply(A,B);
  const x1_ = C.valueOf()[0][0];
  const y1_ = C.valueOf()[1][0];
  console.log(`x1_ ${x1_}, y1_ ${y1_} : ${C.valueOf()}`);

  // step2
  const rx2 = rx*rx;
  const ry2 = ry*ry;
  const x1_2 = x1_*x1_;
  const y1_2 = y1_*y1_;

  const g0 = rx2*ry2 - rx2*y1_2 - ry2*x1_2;
  const g1 = rx2*y1_2 + ry2*x1_2;
  let sq = Math.sqrt(g0/g1);
  let sign = (fA === fS) ? -1.0 : 1.0;
  sq = sq * sign;
  const cx_ = sq * ((rx*y1_)/ry);
  const cy_ = sq * -((ry*x1_)/rx);

  A = math.matrix([[cosPhi, -sinPhi], [sinPhi, cosPhi]]);
  B = math.matrix([[cx_], [cy_]]);
  C = math.multiply(A,B);
  let cx = C.valueOf()[0][0];
  let cy = C.valueOf()[1][0];
  cx += ((x1 + x2) / 2.0);
  cy += ((y1 + y2) / 2.0);
  console.log(`cx: ${cx}, cy: ${cy}`);

  const ux = (x1_ - cx_) / rx;
  const uy = (y1_ - cy_) / ry;
  const vx = (-x1_ - cx_) / rx;
  const vy = (-y1_ - cy_) / ry;
  let n = Math.sqrt((ux*ux) + (uy*uy));
  let p = ux;

  sign = (uy < 0) ? -1.0 : 1.0;
  let sa = 180.0 *(sign * Math.acos(p/n)) / Math.PI;

  n = Math.sqrt((ux*ux + uy*uy) * (vx*vx + vy*vy));
  p = ux*vx + uy*vy;
  sign = (ux*vy - uy*vx < 0) ? -1.0 : 1.0;
  let ea = 180.0 *(sign * Math.acos(p/n)) / Math.PI;
  if( !fS && ea > 0 ){
    ea -= 360.0;
  } else if( fS && ea < 0) {
    ea += 360.0;
  }

  sa %= 360.0;
  ea %= 360.0;

  sa = toRad(sa);
  ea = toRad(ea);
  const clockWise = 1 - fS;

  return {x1, y1, x2, y2, cx, cy, sa, ea, phi, rx, ry, clockWise}
}

代码笔

于 2019-12-23T00:15:35.767 回答