【问题标题】:Mapping SVG ARCTO to HTML Canvas ARCTO将 SVG ARCTO 映射到 HTML 画布 ARCTO
【发布时间】:2011-10-07 10:16:33
【问题描述】:

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

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

【问题讨论】:

    标签: canvas svg html5-canvas


    【解决方案1】:

    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 x y)。所以示例路径是这样的:

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

    Here你可能会发现应该如何绘制贝塞尔曲线。

    现在您有一个上下文点(即 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。它们将相似 您可以使用this 文章获取旋转的数学运算。

    PS:我从this的答案中提取了部分代码

    【讨论】:

    • 我找不到任何关于我的案例的参考资料。
    • 谢谢。方法二更好。方法一需要缩放,这将产生一个不良的副作用。这会导致按比例缩小的描边部分扩大,使曲线变得不均匀。
    • 嗯,我只有 x0,y0 的值,而不是 x1,y1 或 x2,y2 的值​​。嗯...不知道如何映射它们。
    • 稍微修改了答案:)
    • 您对这个答案满意吗?
    【解决方案2】:

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

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

    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);
    }
    

    【讨论】:

      【解决方案3】:

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

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

      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);
      };      
      

      【讨论】:

      • 您有权发布 Gabe 的代码吗? IE。许可证是否允许这样做?
      【解决方案4】:

      我遇到了同样的问题,所以我遇到了这篇文章。 Implementation Requirements Appendix of the W3C SVG definition 准确地讲述了如何将形式(他们称之为)端点参数化转换为中心参数化并返回:

      SVG 弧(端点参数化)由以下方式描述:

      • x1/y1:起始位置(最后一条路径命令的位置)
      • x2/y2:圆弧的结束位置(该路径命令的xy值)
      • rx/ry:x和y半径
      • φ:旋转角度
      • fA:大圆弧标志(1或0,使用大圆弧还是小圆弧)
      • fS:扫标志(顺时针还是逆时针)

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

      • cx/cy:椭圆的中心点
      • rx/ry:x和y半径
      • φ:旋转角度
      • θ1:椭圆的起始角度(旋转前)
      • Δθ:使用椭圆的角度距离(方向取决于扫描标志fS,也可以计算终点θ2,可能是偶数更好)

      从 SVG 转换为画布

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

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

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

        如果 fA ≠ fS 则选择 + 号,并且 - 如果 fA = fS,则选择符号。

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

      4. 计算 θ1 和 Δθ(方程 F.6.5.5 和 F.6.5.6)

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

        其中 θ1 固定在 -360°

        如果 fS = 0,则 Δθ

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

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

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

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

      我现在使用以下方程来确定 θ1 和 Δθ:

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

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

      从画布转换为 SVG

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

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

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

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

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

      3. 找到标志

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

      【讨论】:

        【解决方案5】:

        有点晚了,但这里有一个可以绘制楔形的解决方案,比如饼图。该解决方案使用来自该线程的所有输入。所以我想我会分享。请注意,它没有实现对半径的检查....

        依赖是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}
        }
        

        CodePen

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-05-25
          • 2017-10-31
          • 1970-01-01
          • 2012-09-25
          • 1970-01-01
          • 2011-08-03
          相关资源
          最近更新 更多