【问题标题】:SVG Elliptical Arcs with JavaSVG椭圆弧与Java
【发布时间】:2010-12-20 18:40:23
【问题描述】:

我最近写了一个 java programSVG 文档转换为 HTML/Canvas:很容易翻译诸如

之类的路径
d="M 0 0 L 100 100 z"

类似

GeneralPath L= new GeneralPath();
L.moveTo(0,0);
L.lineTo(100,100);
L.closePath();

但是我不知道如何将 Elliptical Arc 命令转换为 Java/GeneralPath。例如,有谁知道我应该如何将以下命令转换为 Java/GeneralPath ?

d = "M 750 200 a 100 50 135 1 1 250 50"

感谢您的帮助。

【问题讨论】:

标签: java path svg


【解决方案1】:

GeneralPath 中没有直接命令。我用这个功能,

    public static final void arcTo(GeneralPath path, float rx, float ry, float theta, boolean largeArcFlag, boolean sweepFlag, float x, float y) {
            // Ensure radii are valid
            if (rx == 0 || ry == 0) {
                    path.lineTo(x, y);
                    return;
            }
            // Get the current (x, y) coordinates of the path
            Point2D p2d = path.getCurrentPoint();
            float x0 = (float) p2d.getX();
            float y0 = (float) p2d.getY();
            // Compute the half distance between the current and the final point
            float dx2 = (x0 - x) / 2.0f;
            float dy2 = (y0 - y) / 2.0f;
            // Convert theta from degrees to radians
            theta = (float) Math.toRadians(theta % 360f);

            //
            // Step 1 : Compute (x1, y1)
            //
            float x1 = (float) (Math.cos(theta) * (double) dx2 + Math.sin(theta)
                            * (double) dy2);
            float y1 = (float) (-Math.sin(theta) * (double) dx2 + Math.cos(theta)
                            * (double) dy2);
            // Ensure radii are large enough
            rx = Math.abs(rx);
            ry = Math.abs(ry);
            float Prx = rx * rx;
            float Pry = ry * ry;
            float Px1 = x1 * x1;
            float Py1 = y1 * y1;
            double d = Px1 / Prx + Py1 / Pry;
            if (d > 1) {
                    rx = Math.abs((float) (Math.sqrt(d) * (double) rx));
                    ry = Math.abs((float) (Math.sqrt(d) * (double) ry));
                    Prx = rx * rx;
                    Pry = ry * ry;
            }

            //
            // Step 2 : Compute (cx1, cy1)
            //
            double sign = (largeArcFlag == sweepFlag) ? -1d : 1d;
            float coef = (float) (sign * Math
                            .sqrt(((Prx * Pry) - (Prx * Py1) - (Pry * Px1))
                                            / ((Prx * Py1) + (Pry * Px1))));
            float cx1 = coef * ((rx * y1) / ry);
            float cy1 = coef * -((ry * x1) / rx);

            //
            // Step 3 : Compute (cx, cy) from (cx1, cy1)
            //
            float sx2 = (x0 + x) / 2.0f;
            float sy2 = (y0 + y) / 2.0f;
            float cx = sx2
                            + (float) (Math.cos(theta) * (double) cx1 - Math.sin(theta)
                                            * (double) cy1);
            float cy = sy2
                            + (float) (Math.sin(theta) * (double) cx1 + Math.cos(theta)
                                            * (double) cy1);

            //
            // Step 4 : Compute the angleStart (theta1) and the angleExtent (dtheta)
            //
            float ux = (x1 - cx1) / rx;
            float uy = (y1 - cy1) / ry;
            float vx = (-x1 - cx1) / rx;
            float vy = (-y1 - cy1) / ry;
            float p, n;
            // Compute the angle start
            n = (float) Math.sqrt((ux * ux) + (uy * uy));
            p = ux; // (1 * ux) + (0 * uy)
            sign = (uy < 0) ? -1d : 1d;
            float angleStart = (float) Math.toDegrees(sign * Math.acos(p / n));
            // Compute the angle extent
            n = (float) Math.sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy));
            p = ux * vx + uy * vy;
            sign = (ux * vy - uy * vx < 0) ? -1d : 1d;
            float angleExtent = (float) Math.toDegrees(sign * Math.acos(p / n));
            if (!sweepFlag && angleExtent > 0) {
                    angleExtent -= 360f;
            } else if (sweepFlag && angleExtent < 0) {
                    angleExtent += 360f;
            }
            angleExtent %= 360f;
            angleStart %= 360f;

            Arc2D.Float arc = new Arc2D.Float();
            arc.x = cx - rx;
            arc.y = cy - ry;
            arc.width = rx * 2.0f;
            arc.height = ry * 2.0f;
            arc.start = -angleStart;
            arc.extent = -angleExtent;
            path.append(arc, true);
    }

【讨论】:

  • 可以找到 Apache Batik 库 org.apache.batik.ext.awt.geom.ExtendedGeneralPath.computeArc()。不知道方法的原始版权是什么,你得看源代码管理,
  • 如果您担心版权问题,上述算法的结构(包括步骤编号)似乎取自 W3C SVG 工作组的实施要求文档,第 F.6.5 节:xahlee.info/REC-SVG11-20110816/… 其中展示了如何从端点表示(SVG 需要)转换为中心表示(GeneralPath 需要),反之亦然。它还指定了 SVG 如何纠正无效参数,这一点很重要。