Logo Search packages:      
Sourcecode: inkscape version File versions  Download package

interp.py

#!/usr/bin/env python 
'''
Copyright (C) 2005 Aaron Spike, aaron@ekips.org

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
'''
import inkex, cubicsuperpath, simplestyle, copy, math, re, bezmisc

uuconv = {'in':90.0, 'pt':1.25, 'px':1, 'mm':3.5433070866, 'cm':35.433070866, 'pc':15.0}
def numsegs(csp):
      return sum([len(p)-1 for p in csp])
def interpcoord(v1,v2,p):
      return v1+((v2-v1)*p)
def interppoints(p1,p2,p):
      return [interpcoord(p1[0],p2[0],p),interpcoord(p1[1],p2[1],p)]
def pointdistance((x1,y1),(x2,y2)):
      return math.sqrt(((x2 - x1) ** 2) + ((y2 - y1) ** 2))
def bezlenapprx(sp1, sp2):
      return pointdistance(sp1[1], sp1[2]) + pointdistance(sp1[2], sp2[0]) + pointdistance(sp2[0], sp2[1])
def tpoint((x1,y1), (x2,y2), t = 0.5):
      return [x1+t*(x2-x1),y1+t*(y2-y1)]
def cspbezsplit(sp1, sp2, t = 0.5):
      m1=tpoint(sp1[1],sp1[2],t)
      m2=tpoint(sp1[2],sp2[0],t)
      m3=tpoint(sp2[0],sp2[1],t)
      m4=tpoint(m1,m2,t)
      m5=tpoint(m2,m3,t)
      m=tpoint(m4,m5,t)
      return [[sp1[0][:],sp1[1][:],m1], [m4,m,m5], [m3,sp2[1][:],sp2[2][:]]]
def cspbezsplitatlength(sp1, sp2, l = 0.5, tolerance = 0.001):
      bez = (sp1[1][:],sp1[2][:],sp2[0][:],sp2[1][:])
      t = bezmisc.beziertatlength(bez, l, tolerance)
      return cspbezsplit(sp1, sp2, t)
def cspseglength(sp1,sp2, tolerance = 0.001):
      bez = (sp1[1][:],sp1[2][:],sp2[0][:],sp2[1][:])
      return bezmisc.bezierlength(bez, tolerance)     
def csplength(csp):
      total = 0
      lengths = []
      for sp in csp:
            lengths.append([])
            for i in xrange(1,len(sp)):
                  l = cspseglength(sp[i-1],sp[i])
                  lengths[-1].append(l)
                  total += l              
      return lengths, total
def styleunittouu(string):
      unit = re.compile('(%s)$' % '|'.join(uuconv.keys()))
      param = re.compile(r'(([-+]?[0-9]+(\.[0-9]*)?|[-+]?\.[0-9]+)([eE][-+]?[0-9]+)?)')

      p = param.match(string)
      u = unit.search(string) 
      if p:
            retval = float(p.string[p.start():p.end()])
      else:
            retval = 0.0
      if u:
            try:
                  return retval * uuconv[u.string[u.start():u.end()]]
            except KeyError:
                  pass
      return retval
      
def tweenstylefloat(property, start, end, time):
      sp = float(start[property])
      ep = float(end[property])
      return str(sp + (time * (ep - sp)))
def tweenstyleunit(property, start, end, time):
      sp = styleunittouu(start[property])
      ep = styleunittouu(end[property])
      return str(sp + (time * (ep - sp)))
def tweenstylecolor(property, start, end, time):
      sr,sg,sb = parsecolor(start[property])
      er,eg,eb = parsecolor(end[property])
      return '#%s%s%s' % (tweenhex(time,sr,er),tweenhex(time,sg,eg),tweenhex(time,sb,eb))
def tweenhex(time,s,e):
      s = float(int(s,16))
      e = float(int(e,16))
      retval = hex(int(math.floor(s + (time * (e - s)))))[2:]
      if len(retval)==1:
            retval = '0%s' % retval
      return retval
def parsecolor(c):
      r,g,b = '0','0','0'
      if c[:1]=='#':
            if len(c)==4:
                  r,g,b = c[1:2],c[2:3],c[3:4]
            elif len(c)==7:
                  r,g,b = c[1:3],c[3:5],c[5:7]
      return r,g,b

class Interp(inkex.Effect):
      def __init__(self):
            inkex.Effect.__init__(self)
            self.OptionParser.add_option("-e", "--exponent",
                                    action="store", type="float", 
                                    dest="exponent", default=0.0,
                                    help="values other than zero give non linear interpolation")
            self.OptionParser.add_option("-s", "--steps",
                                    action="store", type="int", 
                                    dest="steps", default=5,
                                    help="number of interpolation steps")
            self.OptionParser.add_option("-m", "--method",
                                    action="store", type="int", 
                                    dest="method", default=2,
                                    help="method of interpolation")
            self.OptionParser.add_option("-d", "--dup",
                                    action="store", type="inkbool", 
                                    dest="dup", default=True,
                                    help="duplicate endpaths")    
            self.OptionParser.add_option("--style",
                                    action="store", type="inkbool", 
                                    dest="style", default=True,
                                    help="try interpolation of some style properties")    
      def effect(self):
            exponent = self.options.exponent
            if exponent>= 0:
                  exponent = 1.0 + exponent
            else:
                  exponent = 1.0/(1.0 - exponent)
            steps = [1.0/(self.options.steps + 1.0)]
            for i in range(self.options.steps - 1):
                  steps.append(steps[0] + steps[-1])
            steps = [step**exponent for step in steps]
                  
            paths = {}              
            styles = {}
            for id in self.options.ids:
                  node = self.selected[id]
                  if node.tagName =='path':
                        paths[id] = cubicsuperpath.parsePath(node.attributes.getNamedItem('d').value)
                        styles[id] = simplestyle.parseStyle(node.attributes.getNamedItem('style').value)
                  else:
                        self.options.ids.remove(id)

            for i in range(1,len(self.options.ids)):
                  start = copy.deepcopy(paths[self.options.ids[i-1]])
                  end = copy.deepcopy(paths[self.options.ids[i]])
                  sst = copy.deepcopy(styles[self.options.ids[i-1]])
                  est = copy.deepcopy(styles[self.options.ids[i]])
                  basestyle = copy.deepcopy(sst)

                  #prepare for experimental style tweening
                  if self.options.style:
                        dostroke = True
                        dofill = True
                        styledefaults = {'opacity':'1.0', 'stroke-opacity':'1.0', 'fill-opacity':'1.0',
                                    'stroke-width':'1.0', 'stroke':'none', 'fill':'none'}
                        for key in styledefaults.keys():
                              sst.setdefault(key,styledefaults[key])
                              est.setdefault(key,styledefaults[key])
                        isnotplain = lambda x: not (x=='none' or x[:1]=='#')
                        if isnotplain(sst['stroke']) or isnotplain(est['stroke']) or (sst['stroke']=='none' and est['stroke']=='none'):
                              dostroke = False
                        if isnotplain(sst['fill']) or isnotplain(est['fill']) or (sst['fill']=='none' and est['fill']=='none'):
                              dofill = False
                        if dostroke:
                              if sst['stroke']=='none':
                                    sst['stroke-width'] = '0.0'
                                    sst['stroke-opacity'] = '0.0'
                                    sst['stroke'] = est['stroke'] 
                              elif est['stroke']=='none':
                                    est['stroke-width'] = '0.0'
                                    est['stroke-opacity'] = '0.0'
                                    est['stroke'] = sst['stroke'] 
                        if dofill:
                              if sst['fill']=='none':
                                    sst['fill-opacity'] = '0.0'
                                    sst['fill'] = est['fill'] 
                              elif est['fill']=='none':
                                    est['fill-opacity'] = '0.0'
                                    est['fill'] = sst['fill'] 

                              

                  if self.options.method == 2:
                        #subdivide both paths into segments of relatively equal lengths
                        slengths, stotal = csplength(start)
                        elengths, etotal = csplength(end)
                        lengths = {}
                        t = 0
                        for sp in slengths:
                              for l in sp:
                                    t += l / stotal
                                    lengths.setdefault(t,0)
                                    lengths[t] += 1
                        t = 0
                        for sp in elengths:
                              for l in sp:
                                    t += l / etotal
                                    lengths.setdefault(t,0)
                                    lengths[t] += -1
                        sadd = [k for (k,v) in lengths.iteritems() if v < 0]
                        sadd.sort()
                        eadd = [k for (k,v) in lengths.iteritems() if v > 0]
                        eadd.sort()

                        t = 0
                        s = [[]]
                        for sp in slengths:
                              if not start[0]:
                                    s.append(start.pop(0))
                              s[-1].append(start[0].pop(0))
                              for l in sp:
                                    pt = t
                                    t += l / stotal
                                    if sadd and t > sadd[0]:
                                          while sadd and sadd[0] < t:
                                                nt = (sadd[0] - pt) / (t - pt)
                                                bezes = cspbezsplitatlength(s[-1][-1][:],start[0][0][:], nt)
                                                s[-1][-1:] = bezes[:2]
                                                start[0][0] = bezes[2]
                                                pt = sadd.pop(0)
                                    s[-1].append(start[0].pop(0))
                        t = 0
                        e = [[]]
                        for sp in elengths:
                              if not end[0]:
                                    e.append(end.pop(0))
                              e[-1].append(end[0].pop(0))
                              for l in sp:
                                    pt = t
                                    t += l / etotal
                                    if eadd and t > eadd[0]:
                                          while eadd and eadd[0] < t:
                                                nt = (eadd[0] - pt) / (t - pt)
                                                bezes = cspbezsplitatlength(e[-1][-1][:],end[0][0][:], nt)
                                                e[-1][-1:] = bezes[:2]
                                                end[0][0] = bezes[2]
                                                pt = eadd.pop(0)
                                    e[-1].append(end[0].pop(0))
                        start = s[:]
                        end = e[:]
                  else:
                        #which path has fewer segments?
                        lengthdiff = numsegs(start) - numsegs(end)
                        #swap shortest first
                        if lengthdiff > 0:
                              start, end = end, start
                        #subdivide the shorter path
                        for x in range(abs(lengthdiff)):
                              maxlen = 0
                              subpath = 0
                              segment = 0
                              for y in range(len(start)):
                                    for z in range(1, len(start[y])):
                                          leng = bezlenapprx(start[y][z-1], start[y][z])
                                          if leng > maxlen:
                                                maxlen = leng
                                                subpath = y
                                                segment = z
                              sp1, sp2 = start[subpath][segment - 1:segment + 1]
                              start[subpath][segment - 1:segment + 1] = cspbezsplit(sp1, sp2)
                        #if swapped, swap them back
                        if lengthdiff > 0:
                              start, end = end, start
                  
                  #break paths so that corresponding subpaths have an equal number of segments
                  s = [[]]
                  e = [[]]
                  while start and end:
                        if start[0] and end[0]:
                              s[-1].append(start[0].pop(0))
                              e[-1].append(end[0].pop(0))
                        elif end[0]:
                              s.append(start.pop(0))
                              e[-1].append(end[0][0])
                              e.append([end[0].pop(0)])
                        elif start[0]:
                              e.append(end.pop(0))
                              s[-1].append(start[0][0])
                              s.append([start[0].pop(0)])
                        else:
                              s.append(start.pop(0))
                              e.append(end.pop(0))
      
                  if self.options.dup:
                        steps = [0] + steps + [1]     
                  #create an interpolated path for each interval
                  group = self.document.createElement('svg:g')    
                  self.document.documentElement.appendChild(group)
                  for time in steps:
                        interp = []
                        #process subpaths
                        for ssp,esp in zip(s, e):
                              if not (ssp or esp):
                                    break
                              interp.append([])
                              #process superpoints
                              for sp,ep in zip(ssp, esp):
                                    if not (sp or ep):
                                          break
                                    interp[-1].append([])
                                    #process points
                                    for p1,p2 in zip(sp, ep):
                                          if not (sp or ep):
                                                break
                                          interp[-1][-1].append(interppoints(p1, p2, time))

                        #remove final subpath if empty.
                        if not interp[-1]:
                              del interp[-1]
                        new = self.document.createElement('svg:path')

                        #basic style tweening
                        if self.options.style:
                              basestyle['opacity'] = tweenstylefloat('opacity',sst,est,time)
                              if dostroke:
                                    basestyle['stroke-opacity'] = tweenstylefloat('stroke-opacity',sst,est,time)
                                    basestyle['stroke-width'] = tweenstyleunit('stroke-width',sst,est,time)
                                    basestyle['stroke'] = tweenstylecolor('stroke',sst,est,time)
                              if dofill:
                                    basestyle['fill-opacity'] = tweenstylefloat('fill-opacity',sst,est,time)
                                    basestyle['fill'] = tweenstylecolor('fill',sst,est,time)
                        new.setAttribute('style', simplestyle.formatStyle(basestyle))
                        new.setAttribute('d', cubicsuperpath.formatPath(interp))
                        group.appendChild(new)

e = Interp()
e.affect()

Generated by  Doxygen 1.6.0   Back to index