【发布时间】:2017-01-08 21:11:38
【问题描述】:
我正在尝试将 SVG 弧线转换为一系列线段。背景是,我想使用 (reportlab)[http://www.reportlab.com/].
svg 给了我这些参数(根据here)。
rx,ry,x轴旋转,大圆弧标志,扫描标志,dx,dy
现在我需要确定这条弧线之后的线。但我不明白如何将其转换为更实用的几何图形。
如何确定椭圆弧的中心及其旋转?
【问题讨论】:
我正在尝试将 SVG 弧线转换为一系列线段。背景是,我想使用 (reportlab)[http://www.reportlab.com/].
svg 给了我这些参数(根据here)。
rx,ry,x轴旋转,大圆弧标志,扫描标志,dx,dy
现在我需要确定这条弧线之后的线。但我不明白如何将其转换为更实用的几何图形。
如何确定椭圆弧的中心及其旋转?
【问题讨论】:
SVG 椭圆弧真的很棘手,我花了一些时间来实现它(即使遵循 SVG 规范)。我最终在 C++ 中得到了类似的东西:
//---------------------------------------------------------------------------
class svg_usek // virtual class for svg_line types
{
public:
int pat; // svg::pat[] index
virtual void reset(){};
virtual double getl (double mx,double my){ return 1.0; };
virtual double getdt(double dl,double mx,double my){ return 0.1; };
virtual void getpnt(double &x,double &y,double t){};
virtual void compute(){};
virtual void getcfg(AnsiString &nam,AnsiString &dtp,AnsiString &val){};
virtual void setcfg(AnsiString &nam,AnsiString &dtp,AnsiString &val,int &an,int &ad,int &av){};
};
//---------------------------------------------------------------------------
class svg_ela:public svg_usek // sweep = 0 arc goes from line p0->p1 CW
{ // sweep = 1 arc goes from line p0->p1 CCW
public: // larc is unused if |da|=PI
double x0,y0,x1,y1,a,b,alfa; int sweep,larc;
double sx,sy,a0,a1,da,ang; // sx,sy rotated center by ang
double cx,cy; // real center
void reset() { x0=0; y0=0; x1=0; y1=0; a=0; b=0; alfa=0; sweep=false; larc=false; compute(); }
double getl (double mx,double my);
// double getdt(double dl,double mx,double my);
double getdt(double dl,double mx,double my) { int n; double dt; dt=divide(dl,getl(mx,my)); n=floor(divide(1.0,dt)); if (n<1) n=1; return divide(1.0,n); }
void getpnt(double &x,double &y,double t);
void compute();
void getcfg(AnsiString &nam,AnsiString &dtp,AnsiString &val);
void setcfg(AnsiString &nam,AnsiString &dtp,AnsiString &val,int &an,int &ad,int &av);
svg_ela() {}
svg_ela(svg_ela& a) { *this=a; }
~svg_ela() {}
svg_ela* operator = (const svg_ela *a) { *this=*a; return this; }
//svg_ela* operator = (const svg_ela &a) { ...copy... return this; }
};
//---------------------------------------------------------------------------
void svg_ela::getpnt(double &x,double &y,double t)
{
double c,s,xx,yy;
t=a0+(da*t);
xx=sx+a*cos(t);
yy=sy+b*sin(t);
c=cos(-ang);
s=sin(-ang);
x=xx*c-yy*s;
y=xx*s+yy*c;
}
//---------------------------------------------------------------------------
void svg_ela::compute()
{
double ax,ay,bx,by; // body
double vx,vy,l,db;
int _sweep;
double c,s,e;
ang=pi-alfa;
_sweep=sweep;
if (larc) _sweep=!_sweep;
e=divide(a,b);
c=cos(ang);
s=sin(ang);
ax=x0*c-y0*s;
ay=x0*s+y0*c;
bx=x1*c-y1*s;
by=x1*s+y1*c;
ay*=e; // transform to circle
by*=e;
sx=0.5*(ax+bx); // mid point between A,B
sy=0.5*(ay+by);
vx=(ay-by);
vy=(bx-ax);
l=divide(a*a,(vx*vx)+(vy*vy))-0.25;
if (l<0) l=0;
l=sqrt(l);
vx*=l;
vy*=l;
if (_sweep)
{
sx+=vx;
sy+=vy;
}
else{
sx-=vx;
sy-=vy;
}
a0=atanxy(ax-sx,ay-sy);
a1=atanxy(bx-sx,by-sy);
// ay=divide(ay,e);
// by=divide(by,e);
sy=divide(sy,e);
da=a1-a0;
if (fabs(fabs(da)-pi)<=_acc_zero_ang) // half arc is without larc and sweep is not working instead change a0,a1
{
db=(0.5*(a0+a1))-atanxy(bx-ax,by-ay);
while (db<-pi) db+=pi2; // db<0 CCW ... sweep=1
while (db>+pi) db-=pi2; // db>0 CW ... sweep=0
_sweep=0;
if ((db<0.0)&&(!sweep)) _sweep=1;
if ((db>0.0)&&( sweep)) _sweep=1;
if (_sweep)
{
// a=0; b=0;
if (da>=0.0) a1-=pi2;
if (da< 0.0) a0-=pi2;
}
}
else if (larc) // big arc
{
if ((da< pi)&&(da>=0.0)) a1-=pi2;
if ((da>-pi)&&(da< 0.0)) a0-=pi2;
}
else{ // small arc
if (da>+pi) a1-=pi2;
if (da<-pi) a0-=pi2;
}
da=a1-a0;
// realny stred
c=cos(+ang);
s=sin(+ang);
cx=sx*c-sy*s;
cy=sx*s+sy*c;
}
//---------------------------------------------------------------------------
atanxy(x,y) 与 atan2(y,x) 相同。你可以忽略类svg_usek。 svg_ela 的用法很简单,首先将 SVG 参数提供给它:
x0,y0 是起点(来自前一个 <path> 元素)x1,y1 是端点 (x0+dx,y0+dy)a,b 和你一样 rx,ry
alfa旋转角度[rad]所以你需要从度数转换...sweep,larc 是你的。然后调用svg_ela::compute();,它将计算插值所需的所有变量。完成此初始化后,只需调用 svg_ela::getpnt(x,y,t); 即可从弧中获取任何点,其中 x,y 是返回的坐标,t=<0,1> 是输入参数。所有其他方法对您来说都不重要。要渲染你的 ARC,只需这样做:
svg_ela arc; // your initialized arc here
int e; double x,y,t;
arc.getpnt(x,y,0.0);
Canvas->MoveTo(x,y);
for (e=1,t=0.0;e;t+=0.02)
{
if (t>=1.0) { t=1.0; e=0; }
arc.getpnt(x,y,t);
Canvas->LineTo(x,y);
}
不要忘记 SVG <g> 和 <path> 可以具有变换矩阵,因此您应该在每次 svg_ela::getpnt(x,y,t) 调用后应用它们。
如果您对这些东西的工作原理感兴趣compute(),只需:
旋转空间,使椭圆半轴对齐。
缩放空间,使椭圆变成圆形。
计算圆的中心点
中心位于垂直于线(x0,y0),(x1,y1) 的线上,也位于其中点上。距离由 Pytagoras 和 sweep 和 larc 组合的方向计算。
缩小为椭圆
向后旋转
现在我们有了真实的中心位置,所以还要计算相对于它的真实端点角度。现在对于椭圆上的每个点,通过椭圆的标准参数方程计算它就足够了,然后旋转到所需的位置,这就是 getpnt(x,y,t) 所做的。
希望对你有所帮助。
这里相关的QA:
一些图片解释了 SVG 弧背后的数学原理(使用与此处相同的变量名)
【讨论】:
body 表示points
对于我的 Java SVG 应用程序,我需要将路径弧转换为直线。我使用上面的代码并将其转换为 Java 类并进行了一些清理。
package de.berndbock.tinysvg.helper;
/**
* Breaks down SVG arcs into line segments.
*
* @author Bernd Bock <chef@bernd-bock.de>
*/
public class ArcSegmenter {
private static final double PI2 = Math.PI * 2;
private static final double ACC_ZERO_ANG = 0.000001 * Math.PI / 180.0;
private final double x0;
private final double y0;
private final double x1;
private final double y1;
private final double a;
private final double b;
private final double alfa;
private final boolean sweep;
private final boolean larc;
private double sx, sy, a0, a1, da, ang; // sx, sy rotated center by ang
// private double cx, cy; // real center
public ArcSegmenter(double x0, double y0, double x1, double y1 , double a, double b, double alfa, int sweep, int larc) {
this.x0 = x0;
this.y0 = y0;
this.x1 = x1;
this.y1 = y1;
this.a = a;
this.b = b;
this.alfa = alfa;
this.sweep = sweep != 0;
this.larc = larc != 0;
compute();
}
private void compute() {
double ax, ay, bx, by; // body
double vx, vy, l, db;
boolean _sweep;
double c, s, e;
ang = Math.PI - alfa;
_sweep = sweep;
if (larc) {
_sweep = !_sweep;
}
e = a / b;
c = Math.cos(ang);
s = Math.sin(ang);
ax = x0 * c - y0 * s;
ay = x0 * s + y0 * c;
bx = x1 * c - y1 * s;
by = x1 * s + y1 * c;
ay *= e; // transform to circle
by *= e;
sx = 0.5 * (ax + bx); // mid point between A,B
sy = 0.5 * (ay + by);
vx = (ay - by);
vy = (bx - ax);
l = a * a / (vx * vx + vy * vy) - 0.25;
if (l < 0) {
l = 0;
}
l = Math.sqrt(l);
vx *= l;
vy *= l;
if (_sweep) {
sx += vx;
sy += vy;
}
else {
sx -= vx;
sy -= vy;
}
a0 = Math.atan2(ay - sy, ax - sx);
a1 = Math.atan2(by - sy, bx - sx);
sy = sy / e;
da = a1 - a0;
if (Math.abs(Math.abs(da) - Math.PI) <= ACC_ZERO_ANG) { // half arc is without larc and sweep is not working instead change a0,a1
db = (0.5 * (a0 + a1)) - Math.atan2(by - ay, bx - ax);
while (db < -Math.PI) {
db += PI2; // db<0 CCW ... sweep=1
}
while (db > Math.PI) {
db -= PI2; // db>0 CW ... sweep=0
}
_sweep = false;
if ((db < 0.0) && (!sweep)) {
_sweep = true;
}
if ((db > 0.0) && ( sweep)) {
_sweep = true;
}
if (_sweep) {
if (da >= 0.0) {
a1 -= PI2;
}
if (da < 0.0) {
a0 -= PI2;
}
}
}
else if (larc) { // big arc
if ((da < Math.PI) && (da >= 0.0)) {
a1 -= PI2;
}
if ((da > -Math.PI) && (da < 0.0)) {
a0 -= PI2;
}
}
else { // small arc
if (da > Math.PI) {
a1 -= PI2;
}
if (da < -Math.PI) {
a0 -= PI2;
}
}
da = a1 - a0;
// center point calculation:
// c = Math.cos(ang);
// s = Math.sin(ang);
// cx = sx * c - sy * s;
// cy = sx * s + sy * c;
}
public Point getpnt(double t) {
Point result = new Point();
double c, s, x, y;
t = a0 + da * t;
x = sx + a * Math.cos(t);
y = sy + b * Math.sin(t);
c = Math.cos(-ang);
s = Math.sin(-ang);
result.x = x * c - y * s;
result.y = x * s + y * c;
return result;
}
// public Point getCenterPoint() {
// return new Point(cx, cy);
// }
}
如果您需要中心点,请取消注释相应的行。 示例代码让您了解用法:
ArcSegmenter segmenter = new ArcSegmenter(currentPoint.x, currentPoint.y, endPoint.x, endPoint.y, rx, ry, phi, sf, lf);
Point p1, p2;
p1 = segmenter.getpnt(0.0);
Line line;
for (double t = increment; t < 1.000001f; t += increment) {
p2 = segmenter.getpnt(t);
line = new Line(null, parent, p1.x, p1.y, p2.x, p2.y);
elements.add(line);
p1 = p2;
}
【讨论】: