Python-复数-svgpathtools-路径解析工具-核心要义

Python-复数-svgpathtools-路径解析工具-核心要义,第1张

文章目录
    • 1.SVG概述
    • 2.复数
    • 3.工具功能
    • 4.基本用法
      • 4.1.构造函数
      • 4.2.Path编辑
      • 4.3.读SVG
      • 4.4.写SVG
      • 4.5.point函数
      • 4.6.贝塞尔曲线转为 NumPy 多项式对象
      • 4.7.在贝塞尔曲线上的 NumPy 数组 *** 作
        • 4.7.1.切向量
        • 4.7.2.平移(移位)、反转方向和法线向量
        • 4.7.2.旋转和平移
        • 4.7.3.弧长和反弧长
        • 4.7.4.贝塞尔曲线之间的交点
        • 4.7.5.路径偏移
    • 5.作者答疑

1.SVG概述

SVG是一种图形文件格式,它的英文全称为Scalable Vector Graphics,意思为可缩放的矢量图形。它是基于XML(Extensible Markup Language),由World Wide Web Consortium(W3C)联盟进行开发的。严格来说应该是一种开放标准的矢量图形语言,可让你设计激动人心的、高分辨率的Web图形页面。用户可以直接用代码来描绘图像,可以用任何文字处理工具打开SVG图像,通过改变部分代码来使图像具有交互功能,并可以随时插入到HTML中通过浏览器来观看。

2.复数

复数(Complex)是 Python 的内置类型,直接书写即可。换句话说,Python 语言本身就支持复数,而不依赖于标准库或者第三方库。复数在 Python 内部的类型是 complex,Python 默认支持对复数的简单计算。复数由实部(real)和虚部(imag)构成,在 Python 中,复数的虚部以j或者J作为后缀,具体格式为:

a + bj
a 表示实部,b 表示虚部。

3.工具功能

svgpathtools 包含读取、写入和显示 SVG 文件的功能,以及用于转换和分析路径元素的几何工具。它由numpy、svgwrite和 scipy (optional, but recommended for performance)组成。它包含如下功能:

  • 读取、写入和显示包含 Path(和其他)SVG 元素的 SVG 文件
  • 将贝塞尔路径段转换为numpy.poly1d(多项式)对象
  • 将多项式(标准形式)转换为它们的贝塞尔形式
  • 计算切向量和(右手法则)法向量
  • 计算曲率
  • 将不连续的路径分解成它们的连续子路径。
  • 有效地计算路径和/或线段之间的交叉点
  • 找到路径或段的边界框
  • 反向段/路径方向
  • 裁剪和拆分路径和段
  • 平滑路径(即消除扭结以使路径可区分)
  • 从路径域到段域并返回(T2t 和 t2T)的转换映射
  • 封闭路径包围的计算区域
  • 计算弧长
  • 计算反弧长
  • 将 RGB 颜色元组转换为十六进制颜色字符串并返回
4.基本用法 4.1.构造函数

svgpathtools 模块主要围绕四个路径段类构建:Line、QuadraticBezier、CubicBezier和Arc。 还有第五类,Path,其对象是(连接或断开1)路径段对象的序列。它们的构造函数如下:

Line(start, end)
Arc(start, radius, rotation, large_arc, sweep, end)
QuadraticBezier(start, control, end)
CubicBezier(start, control1, control2, end)
Path(*segments)

使用范例

# Coordinates are given as points in the complex plane
from svgpathtools import Path, Line, QuadraticBezier, CubicBezier, Arc
seg1 = CubicBezier(300+100j, 100+100j, 200+200j, 200+300j)  # A cubic beginning at (300, 100) and ending at (200, 300)
seg2 = Line(200+300j, 250+350j)  # A line beginning at (200, 300) and ending at (250, 350)
path = Path(seg1, seg2)  # A path traversing the cubic and then the line

# We could alternatively created this Path object using a d-string
from svgpathtools import parse_path
path_alt = parse_path('M 300 100 C 100 100 200 200 200 300 L 250 350')

# Let's check that these two methods are equivalent
print(path)
print(path_alt)
print(path == path_alt)

# On a related note, the Path.d() method returns a Path object's d-string
print(path.d())
print(parse_path(path.d()) == path)
4.2.Path编辑

该类Path是一个多段序列,因此它的行为很像一个列表。所以可以追加、插入、按索引设置、删除、枚举、切片等。

# 末尾添加
path.append(CubicBezier(250+350j, 275+350j, 250+225j, 200+100j))
print(path)

# 替换第一个路径对象
path[0] = Line(200+100j, 200+300j)
print(path)

# 判断是否连续,是否封闭
print("path is continuous? ", path.iscontinuous())
print("path is closed? ", path.isclosed())

# 平滑
from svgpathtools import kinks, smoothed_path
print("path contains non-differentiable points? ", len(kinks(path)) > 0)

# 平滑
spath = smoothed_path(path)
print("spath contains non-differentiable points? ", len(kinks(spath)) > 0)
print(spath)

# 显示路径和切换
from svgpathtools import disvg
from time import sleep
disvg(path) #调用系统默认打开svg文件
sleep(1)  # needed when not giving the SVGs unique names (or not using timestamp)
disvg(spath)
print("Notice that path contains {} segments and spath contains {} segments."
      "".format(len(path), len(spath)))
4.3.读SVG

svg2paths()函数将 svgfile 转换为 Path 对象列表和包含每个所述路径属性的单独字典列表。

注意:Line、Polyline、Polygon 和 Path SVG 元素都可以使用此函数转换为 Path 对象。

# 将 SVG 读入路径对象列表和属性字典列表
from svgpathtools import svg2paths, wsvg
paths, attributes = svg2paths('test.svg')

# 读入属性
from svgpathtools import svg2paths2
paths, attributes, svg_attributes = svg2paths2('test.svg')

# 读入路径对象及相应属性
redpath = paths[0]
redpath_attribs = attributes[0]
print(redpath)
print(redpath_attribs['stroke'])
4.4.写SVG

wsvg() 函数从路径列表创建一个 SVG 文件。这个函数可以做很多事情(有关更多信息,请参见paths2svg.py中的文档字符串)。注意:使用便捷函数disvg()(或设置 ‘openinbrowser=True’)自动尝试在默认 SVG 查看器中打开创建的 svg 文件。在局部查看SVG代码时,有不错的效果。

wsvg(paths, attributes=attributes, svg_attributes=svg_attributes, filename='output1.svg')
4.5.point函数

SVG 路径元素及其段具有官方参数化。可以使用Path.point()、Line.point()、QuadraticBezier.point()、CubicBezier.point()和Arc.point()方法访问这些参数化。所有这些参数化都是在域 0 <= t <= 1 上定义的。它可以取一个参数t,来计算除控制点之外的点。注意:T在本文档以及内联文档和文档中,当提到 Path 对象的参数化时,我使用大写字母,t当提到路径段对象(即 Line、QaudraticBezier、CubicBezier 和 Arc 对象)时,我使用小写字母。给定一个T值,该Path.T2t()方法可用于查找相应的段索引k和段参数t,t使得path.point(T)=path[k].point(t)。还有一种Path.t2T()方法可以解决逆问题。

#范例

#路径检查 第一个元素
firstseg = redpath[0] 
print(redpath.point(0) == firstseg.point(0) == redpath.start == firstseg.start)

#路径检查 最后一个元素
lastseg = redpath[-1] 
print(redpath.point(1) == lastseg.point(1) == redpath.end == lastseg.end)

#point函数
print(redpath.point(0.5) == firstseg.point(0.5))

# If we want to figure out which segment of redpoint the 
# point redpath.point(0.5) lands on, we can use the path.T2t() method
k, t = redpath.T2t(0.5)
print(redpath[k].point(t) == redpath.point(0.5))
4.6.贝塞尔曲线转为 NumPy 多项式对象

另一种好方法将Line和QuadraticBezier对象参数化是将它们转换为numpy.poly1d对象。使用Line.poly(),QuadraticBezier.poly()和ubicBezier.poly()函数很容易做到这一点。pathtools.py 子模块中还有一个polynomial2bezier()函数可以将多项式转换回贝塞尔曲线。
注意:三次贝塞尔曲线参数化为
B ( t ) = P 0 ( 1 − t ) 3 + 3 P 1 ( 1 − t ) 2 t + 3 P 2 ( 1 − t ) t 2 + P 3 t 3 \mathcal{B}(t) = P_0(1-t)^3 + 3P_1(1-t)^2t + 3P_2(1-t)t^2 + P_3t^3 B(t)=P0(1t)3+3P1(1t)2t+3P2(1t)t2+P3t3
其中 P 0 P_0 P0 P 1 P_1 P1 P 2 P_2 P2 P 3 P_3 P3分别是 svgpathtools 用于定义 CubicBezier 对象的控制点start、control1、control2和end。CubicBezier.poly()方法将此多项式扩展为其标准形式
B ( t ) = c 0 t 3 + c 1 t 2 + c 2 t + c 3 \mathcal{B}(t) = c_0t^3 + c_1t^2 +c_2t+c3 B(t)=c0t3+c1t2+c2t+c3
QuadraticBezier.poly() and Line.poly() 有着相似定义。

# 例子
b = CubicBezier(300+100j, 100+100j, 200+200j, 200+300j)
p = b.poly() #标准多项式

# p(t) == b.point(t)
print(p(0.235) == b.point(0.235))

# What is p(t)?  It's just the cubic b written in standard form.
bpretty = "{}*(1-t)^3 + 3*{}*(1-t)^2*t + 3*{}*(1-t)*t^2 + {}*t^3".format(*b.bpoints())
print("The CubicBezier, b.point(x) = \n\n" +
      bpretty + "\n\n" +
      "can be rewritten in standard form as \n\n" +
      str(p).replace('x','t'))

结果如下:

True
The CubicBezier, b.point(x) = 
(300+100j)*(1-t)^3 + 3*(100+100j)*(1-t)^2*t + 3*(200+200j)*(1-t)*t^2 + (200+300j)*t^3
can be rewritten in standard form as 
                3                2
(-400 + -100j) t + (900 + 300j) t - 600 t + (300 + 100j)
4.7.在贝塞尔曲线上的 NumPy 数组 *** 作

为了进一步说明能够将 Bezier 曲线对象转换为 numpy.poly1d 对象并返回的能力,让我们以四种不同的方式计算上述 CubicBezier 对象 b 在 t=0.5 处的单位切线向量。

4.7.1.切向量
from svgpathtools import Path, Line, QuadraticBezier, CubicBezier, Arc
from svgpathtools import disvg
svgfilename=r"test.svg";
from svgpathtools import svg2paths, wsvg

#范例数据
b = CubicBezier(300+100j, 100+100j, 200+200j, 200+300j)
p = b.poly()

t = 0.5
### Method 1: the easy way
u1 = b.unit_tangent(t) #单位切线

#如果遇到可移动奇点,这种方法就会失败  
u2 = b.derivative(t)/abs(b.derivative(t)) #返回段在t处的n阶导数

#如果遇到可移动奇点,这种方法就会失败  
dp = p.deriv()
u3 = dp(t)/abs(dp(t)) #返回导数

### Method 4: the removable-singularity-proof numpy.poly1d way
# Note: This is roughly how Method 1 works
from svgpathtools import real, imag, rational_limit
dx, dy = real(dp), imag(dp)  # dp == dx + 1j*dy
p_mag2 = dx**2 + dy**2  # p_mag2(t) = |p(t)|**2
# Note: abs(dp) isn't a polynomial, but abs(dp)**2 is, and,
#  the limit_{t->t0}[f(t) / abs(f(t))] ==
# sqrt(limit_{t->t0}[f(t)**2 / abs(f(t))**2])
from cmath import sqrt
u4 = sqrt(rational_limit(dp**2, p_mag2, t))

print("unit tangent check:", u1 == u2 == u3 == u4)

# Let's do a visual check
mag = b.length()/4  # so it's not hard to see the tangent line
tangent_line = Line(b.point(t), b.point(t) + mag*u1)
disvg([b, tangent_line], 'bg', nodes=[b.point(t)])

disvg函数定义:

def disvg(paths=None, colors=None, filename=None, stroke_widths=None,
nodes=None, node_colors=None, node_radii=None,
openinbrowser=True, timestamp=None, margin_size=0.1,
mindim=600, dimensions=None, viewbox=None, text=None,
text_path=None, font_size=None, attributes=None,
svg_attributes=None, svgwrite_debug=False,
paths2Drawing=False, baseunit=‘px’):

4.7.2.平移(移位)、反转方向和法线向量
from svgpathtools import Path, Line, QuadraticBezier, CubicBezier, Arc
from svgpathtools import disvg
svgfilename=r"test.svg";
from svgpathtools import svg2paths, wsvg

# 例子
b = CubicBezier(300+100j, 100+100j, 200+200j, 200+300j)
p = b.poly()
t=0.5;
u1 = b.unit_tangent(t) #单位切线

#显示切线
mag = b.length()/4  # so it's not hard to see the tangent line
tangent_line = Line(b.point(t), b.point(t) + mag*u1)
#disvg([b, tangent_line], 'bg', nodes=[b.point(t)])

#法向量
n = b.normal(t)
normal_line = Line(b.point(t), b.point(t) + mag*n)
disvg([b, tangent_line, normal_line], 'bgp', nodes=[b.point(t)])

#路径反向
br = b.reversed()

# Let's also shift b_r over a bit to the right so we can view it next to b
# The simplest way to do this is br = br.translated(3*mag),  but let's use
# the .bpoints() instead, which returns a Bezier's control points
br.start, br.control1, br.control2, br.end = [3*mag + bpt for bpt in br.bpoints()]  #

#反向结果写入文件
tangent_line_r = Line(br.point(t), br.point(t) + mag*br.unit_tangent(t))
normal_line_r = Line(br.point(t), br.point(t) + mag*br.normal(t))
wsvg([b, tangent_line, normal_line, br, tangent_line_r, normal_line_r],
     'bgpkgp', nodes=[b.point(t), br.point(t)], filename='vectorframes.svg',
     text=["b's tangent", "br's tangent"], text_path=[tangent_line, tangent_line_r])
4.7.2.旋转和平移
from svgpathtools import Path, Line, QuadraticBezier, CubicBezier, Arc
from svgpathtools import disvg
from svgpathtools import svg2paths, wsvg
#创建一个弧段和一条直线
top_half = Arc(start=-1, radius=1+2j, rotation=0, large_arc=1, sweep=1, end=1)
midline = Line(-1.5, 1.5)

#两条圆弧构件椭圆
bottom_half = top_half.rotated(180)
decorated_ellipse = Path(top_half, bottom_half)

#椭圆内增加旋转的直线
for k in range(12):
    decorated_ellipse.append(midline.rotated(30*k))
    
#平移路径然后最终输出
decorated_ellipse = decorated_ellipse.translated(4+0j)
wsvg([top_half, midline, decorated_ellipse], filename='decorated_ellipse.svg')
4.7.3.弧长和反弧长

在这里,我们将创建一个 SVG 来展示路径的参数和几何中点test.svg。我们需要计算使用Path.length()、Line.length()、QuadraticBezier.length()、CubicBezier.length()和Arc.length()方法,以及相关的反弧长方法.ilength()函数来执行此 *** 作。

from svgpathtools import Path, Line, QuadraticBezier, CubicBezier, Arc
from svgpathtools import disvg
from svgpathtools import svg2paths, wsvg
#读取数据
paths, attributes = svg2paths('test.svg')

# 标记每一段的中点
min_depth = 5
error = 1e-4
dots = []
ncols = []
nradii = []
for path in paths:
    for seg in path:
        parametric_mid = seg.point(0.5)
        seg_length = seg.length()
        if seg.length(0.5)/seg.length() == 1/2:
            dots += [parametric_mid]
            ncols += ['purple']
            nradii += [5]
        else:
            t_mid = seg.ilength(seg_length/2)
            geo_mid = seg.point(t_mid)
            dots += [parametric_mid, geo_mid]
            ncols += ['red', 'green']
            nradii += [5] * 2

# paths将保持原来的属性
wsvg(paths, nodes=dots, node_colors=ncols, node_radii=nradii, 
     attributes=attributes, filename='output2.svg')
4.7.4.贝塞尔曲线之间的交点
from svgpathtools import Path, Line, QuadraticBezier, CubicBezier, Arc
from svgpathtools import disvg
from svgpathtools import svg2paths, wsvg
#读取数据
paths, attributes = svg2paths('test.svg')

# 查找红色路径和其它路径的交点
redpath = paths[0]
redpath_attribs = attributes[0]
intersections = []
for path in paths[1:]:
    for (T1, seg1, t1), (T2, seg2, t2) in redpath.intersect(path):
        intersections.append(redpath.point(T1))

disvg(paths, filename='output_intersections.svg', attributes=attributes,
      nodes=intersections, node_radii=[5] * len(intersections))
4.7.5.路径偏移
from svgpathtools import parse_path, Line, Path, wsvg
def offset_curve(path, offset_distance, steps=1000):
    """Takes in a Path object, `path`, and a distance,
    `offset_distance`, and outputs an piecewise-linear approximation 
    of the 'parallel' offset curve."""
    nls = []
    for seg in path:
        ct = 1
        for k in range(steps):
            t = k / steps
            offset_vector = offset_distance * seg.normal(t)
            nl = Line(seg.point(t), seg.point(t) + offset_vector)
            nls.append(nl)
    connect_the_dots = [Line(nls[k].end, nls[k+1].end) for k in range(len(nls)-1)]
    if path.isclosed():
        connect_the_dots.append(Line(nls[-1].end, nls[0].end))
    offset_path = Path(*connect_the_dots)
    return offset_path

# Examples:
path1 = parse_path("m 288,600 c -52,-28 -42,-61 0,-97 ")
path2 = parse_path("M 151,395 C 407,485 726.17662,160 634,339").translated(300)
path3 = parse_path("m 117,695 c 237,-7 -103,-146 457,0").translated(500+400j)
paths = [path1, path2, path3]

offset_distances = [10*k for k in range(1,51)]
offset_paths = []
for path in paths:
    for distances in offset_distances:
        offset_paths.append(offset_curve(path, distances))

# Let's take a look
wsvg(paths + offset_paths, 'g'*len(paths) + 'r'*len(offset_paths), filename='offset_curves.svg')

5.作者答疑

  如有疑问,请留言。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://www.outofmemory.cn/langs/788356.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-05
下一篇 2022-05-05

发表评论

登录后才能评论

评论列表(0条)

保存