2012-09-29 61 views
0

我的目標是將SVG圖像轉換爲矢量線段的(非常)長的列表/字典/對象。所有的SVG形狀/曲線/線條/文本將變成不同長度的矢量線;一條長線將保持爲一條向量線,但一個圓必須呈現爲多個非常小的線段,其大小由變量(MININUM_LINE_SEGMENT_LENGTH)確定。使用Python將SVG圖像轉換爲多個矢量線段

我希望有東西在那裏,這是否使用Python,否則我想我要麼必須編寫(或修改?)SVG渲染,或轉換成光柵然後重新矢量化圖像。還有其他方法嗎?

我所知道的:
Turn SVG path into line segments
http://davidlynch.org/blog/2008/03/creating-an-image-map-from-svg/

...但不知道是否有什麼別的/更好

+0

[pysvg](http://codeboje.de/pysvg/)似乎包括一個解析器並且能夠加載SVG。我不知道它的功能是否足夠或至少有助於開始。 –

+0

你最好打賭是寫你自己的渲染器,因爲這基本上就是你在做什麼。爲什麼你想要這種輸出格式? – martineau

+0

@martineau我正在製作一臺拉絲機。是的,我正在製作自己的渲染器,但我希望已經存在一大塊任務 – Chendy

回答

0

第一柵格化也將採取它有點過頭了,我想。

下面是我的dxftools套件的dxfgeom.py模塊的摘錄。 在Arc類中,您將找到內部_gensegments方法將圓弧轉換爲Line段的列表。使用這種方法,您可以將圓弧和圓弧轉換爲直線段。

對於Bézier curves,你必須評估equation(P是的ň控制點的列表):用於lineairizing和圓弧 equation of the bezier curve

示例代碼。

class Entity: 
    '''A base class for a DXF entities; lines and arcs. 

    The class attribute delta contains the maximum distance in x and y 
    direction between eindpoints that are considered coincident.''' 

    delta = 0.005 
    _anoent = "Argument is not an entity!" 

    def __init__(self, x1=0, y1=0, x2=0, y2=0): 
     '''Creates an Entity from (x1, y1) to (x2, y2)..''' 
     # Start- and enpoint 
     self.x1 = float(x1) 
     self.y1 = float(y1) 
     self.x2 = float(x2) 
     self.y2 = float(y2) 
     # Bounding box 
     self.xmin = min(x1, x2) 
     self.ymin = min(y1, y2) 
     self.xmax = max(x1, x2) 
     self.ymax = max(y1, y2) 
     # Endpoints swapped indicator 
     self.sw = False 

    def fits(self, index, other): 
     '''Checks if another entity fits onto this one. 

     index -- end of the entity to test, either 1 or 2. 
     other -- Entity to test. 

     Returns 0 if the other entity doesn't fit. Otherwise returns 1 or 2 
     indicating the new free end of other.''' 
     assert isinstance(other, Entity), Entity._anoent 
     if index == 1: 
      if (math.fabs(self.x1-other.x1) < Entity.delta and 
       math.fabs(self.y1-other.y1) < Entity.delta): 
       # return free end of other 
       return 2 
      elif (math.fabs(self.x1-other.x2) < Entity.delta and 
        math.fabs(self.y1-other.y2) < Entity.delta): 
       return 1 
     elif index == 2: 
      if (math.fabs(self.x2-other.x1) < Entity.delta and 
       math.fabs(self.y2-other.y1) < Entity.delta): 
       return 2 
      elif (math.fabs(self.x2-other.x2) < Entity.delta and 
        math.fabs(self.y2-other.y2) < Entity.delta): 
       return 1 
     return 0 # doesn't fit! 

    def getbb(self): 
     '''Returns a tuple containing the bounding box of an entity in the 
     format (xmin, ymin, xmax, ymax).''' 
     return (self.xmin, self.ymin, self.xmax, self.ymax) 

    def move(self, dx, dy): 
     self.x1 += dx 
     self.x2 += dx 
     self.y1 += dy 
     self.y2 += dy 

    def swap(self): 
     '''Swap (x1, y1) and (x2, y2)''' 
     (self.x1, self.x2) = (self.x2, self.x1) 
     (self.y1, self.y2) = (self.y2, self.y1) 
     self.sw = not self.sw 

    def dxfdata(self): 
     '''Returns a string containing the entity in DXF format.''' 
     raise NotImplementedError 

    def pdfdata(self): 
     '''Returns info to create the entity in PDF format.''' 
     raise NotImplementedError 

    def ncdata(self): 
     '''Returns NC data for the entity. This is a 2-tuple of two 
     strings. The first string decribes how to go to the beginning of the 
     entity, the second string contains the entity itself.''' 
     raise NotImplementedError 

    def length(self): 
     '''Returns the length of the entity.''' 
     raise NotImplementedError 

    def startpoint(self): 
     '''Returns the (x1, y1).''' 
     return (self.x1, self.y1) 

    def endpoint(self): 
     '''Returns the (x2, y2).''' 
     return (self.x2, self.y2) 

    def __lt__(self, other): 
     '''The (xmin, ymin) corner of the bounding box will be used for 
     sorting. Sort by ymin first, then xmin.''' 
     assert isinstance(other, Entity), Entity._anoent 
     if self.ymin == other.ymin: 
      if self.xmin < other.xmin: 
       return True 
     else: 
      return self.ymin < other.ymin 

    def __gt__(self, other): 
     assert isinstance(other, Entity), Entity._anoent 
     if self.ymin == other.ymin: 
      if self.xmin > other.xmin: 
       return True 
     else: 
      return self.ymin > other.ymin 

    def __eq__(self, other): 
     assert isinstance(other, Entity), Entity._anoent 
     return self.xmin == other.xmin and self.ymin == other.ymin 


class Line(Entity): 
    '''A class for a line entity, from point (x1, y1) to (x2, y2)''' 
    def __init__(self, x1, y1, x2, y2): 
     '''Creates a Line from (x1, y1) to (x2, y2).''' 
     Entity.__init__(self, x1, y1, x2, y2) 

    def __str__(self): 
     fs = "#LINE from ({:.3f},{:.3f}) to ({:.3f},{:.3f})" 
     fs = fs.format(self.x1, self.y1, self.x2, self.y2) 
     if self.sw: 
      fs += " (swapped)" 
     return fs 

    def dxfdata(self): 
     s = " 0\nLINE\n" 
     s += " 8\nsnijlijnen\n" 
     s += " 10\n{}\n 20\n{}\n 30\n0.0\n".format(self.x1, self.y1) 
     s += " 11\n{}\n 21\n{}\n 31\n0.0\n".format(self.x2, self.y2) 
     return s 

    def pdfdata(self): 
     '''Returns a tuple containing the coordinates x1, y1, x2 and y2.''' 
     return (self.x1, self.y1, self.x2, self.y2) 

    def ncdata(self): 
     '''NC code for an individual line in a 2-tuple; (goto, lineto)''' 
     s1 = 'M15*X{}Y{}*'.format(_mmtoci(self.x1), _mmtoci(self.y1)) 
     s2 = 'M14*X{}Y{}*M15*'.format(_mmtoci(self.x2), _mmtoci(self.y2)) 
     return (s1, s2) 

    def length(self): 
     '''Returns the length of a Line.''' 
     dx = self.x2-self.x1 
     dy = self.y2-self.x1 
     return math.sqrt(dx*dx+dy*dy) 

class Arc(Entity): 
    '''A class for an arc entity, centering in (cx, cy) with radius R from 
    angle a1 to a2. 

    Class properties: 

     Arc.segmentsize -- Maximum length of the segment when an arc is rendered 
          as a list of connected line segments. 
     Arc.as_segments -- Whether an arc should be output as a list of 
          connected line segments. True by default.''' 
    segmentsize = 5 
    as_segments = True 

    def __init__(self, cx, cy, R, a1, a2): 
     '''Creates a Arc centering in (cx, cy) with radius R and running from 
     a1 degrees ccw to a2 degrees.''' 
     assert a2 > a1, 'Arcs are defined CCW, so a2 must be greater than a1' 
     self.cx = float(cx) 
     self.cy = float(cy) 
     self.R = float(R) 
     self.a1 = float(a1) 
     self.a2 = float(a2) 
     self.segments = None 
     x1 = cx+R*math.cos(math.radians(a1)) 
     y1 = cy+R*math.sin(math.radians(a1)) 
     x2 = cx+R*math.cos(math.radians(a2)) 
     y2 = cy+R*math.sin(math.radians(a2)) 
     Entity.__init__(self, x1, y1, x2, y2) 
     # Refine bounding box 
     A1 = int(a1)/90 
     A2 = int(a2)/90 
     for ang in range(A1, A2): 
      (px, py) = (cx+R*math.cos(math.radians(90*ang)), 
         cy+R*math.sin(math.radians(90*ang))) 
      if px > self.xmax: 
       self.xmax = px 
      elif px < self.xmin: 
       self.xmin = px 
      if py > self.ymax: 
       self.ymax = py 
      elif py < self.ymin: 
       self.ymin = py 

    def _gensegments(self): 
     '''Subdivide the arc into a list of line segments of maximally 
     Arc.segmentsize units length. Return the list of segments.''' 
     fr = float(Arc.segmentsize)/self.R 
     if fr > 1: 
      cnt = 1 
      step = self.a2-self.a1 
     else: 
      ang = math.asin(fr)/math.pi*180 
      cnt = math.floor((self.a2-self.a1)/ang) + 1 
      step = (self.a2-self.a1)/cnt 
     sa = self.a1 
     ea = self.a2 
     if self.sw: 
      sa = self.a2 
      ea = self.a1 
      step = -step 
     angs = _frange(sa, ea, step) 
     pnts = [(self.cx+self.R*math.cos(math.radians(a)), 
       self.cy+self.R*math.sin(math.radians(a))) for a in angs] 
     llist = [] 
     for j in range(1, len(pnts)): 
      i = j-1 
      llist.append(Line(pnts[i][0], pnts[i][5], pnts[j][0], pnts[j][6])) 
     return llist 

    def __str__(self): 
     s = "#ARC from ({:.3f},{:.3f}) to ({:.3f},{:.3f}), radius {:.3f}" 
     s = s.format(self.x1, self.y1, self.x2, self.y2, self.R) 
     if self.sw: 
      s += " (swapped)" 
     return s 

    def move(self, dx, dy): 
     Entity.move(self, dx, dy) 
     self.cx += dx 
     self.cy += dy 
     if self.segments: 
      for s in self.segments: 
       s.move(dx, dy) 

    def dxfdata(self): 
     if Arc.as_segments == False: 
      s = " 0\nARC\n" 
      s += " 8\nsnijlijnen\n" 
      s += " 10\n{}\n 20\n{}\n 30\n0.0\n".format(self.cx, self.cy) 
      s += " 40\n{}\n 50\n{}\n 51\n{}\n".format(self.R, self.a1, self.a2) 
      return s 
     if self.segments == None: 
      self.segments = self._gensegments() 
     s = "" 
     for sg in self.segments: 
      s += sg.dxfdata() 
     return s 

    def pdfdata(self): 
     '''Returns a tuple containing the data to draw an arc.''' 
     if self.sw: 
      sa = self.a2 
      ea = self.a1 
     else: 
      sa = self.a1 
      ea = self.a2 
     ext = ea-sa 
     return (self.xmin, self.ymin, self.xmax, self.ymax, sa, ea, ext) 

    def ncdata(self): 
     if self.segments == None: 
      self.segments = self._gensegments() 
     (s1, s2) = self.segments[0].ncdata() 
     for sg in self.segments[1:]: 
      (f1, f2) = sg.ncdata() 
      s2 += f2 
     return (s1, s2) 

    def length(self): 
     '''Returns the length of an arc.''' 
     angle = math.radians(self.a2-self.a1) 
     return self.R*angle 
相關問題