home *** CD-ROM | disk | FTP | other *** search
/ Chip 2011 November / CHIP_2011_11.iso / Programy / Narzedzia / Inkscape / Inkscape-0.48.2-1-win32.exe / share / extensions / interp.py < prev    next >
Text File  |  2011-07-08  |  14KB  |  320 lines

  1. #!/usr/bin/env python 
  2. '''
  3. Copyright (C) 2005 Aaron Spike, aaron@ekips.org
  4.  
  5. This program is free software; you can redistribute it and/or modify
  6. it under the terms of the GNU General Public License as published by
  7. the Free Software Foundation; either version 2 of the License, or
  8. (at your option) any later version.
  9.  
  10. This program is distributed in the hope that it will be useful,
  11. but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13. GNU General Public License for more details.
  14.  
  15. You should have received a copy of the GNU General Public License
  16. along with this program; if not, write to the Free Software
  17. Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  18. '''
  19. import inkex, cubicsuperpath, simplestyle, copy, math, bezmisc, simpletransform
  20.  
  21. def numsegs(csp):
  22.     return sum([len(p)-1 for p in csp])
  23. def interpcoord(v1,v2,p):
  24.     return v1+((v2-v1)*p)
  25. def interppoints(p1,p2,p):
  26.     return [interpcoord(p1[0],p2[0],p),interpcoord(p1[1],p2[1],p)]
  27. def pointdistance((x1,y1),(x2,y2)):
  28.     return math.sqrt(((x2 - x1) ** 2) + ((y2 - y1) ** 2))
  29. def bezlenapprx(sp1, sp2):
  30.     return pointdistance(sp1[1], sp1[2]) + pointdistance(sp1[2], sp2[0]) + pointdistance(sp2[0], sp2[1])
  31. def tpoint((x1,y1), (x2,y2), t = 0.5):
  32.     return [x1+t*(x2-x1),y1+t*(y2-y1)]
  33. def cspbezsplit(sp1, sp2, t = 0.5):
  34.     m1=tpoint(sp1[1],sp1[2],t)
  35.     m2=tpoint(sp1[2],sp2[0],t)
  36.     m3=tpoint(sp2[0],sp2[1],t)
  37.     m4=tpoint(m1,m2,t)
  38.     m5=tpoint(m2,m3,t)
  39.     m=tpoint(m4,m5,t)
  40.     return [[sp1[0][:],sp1[1][:],m1], [m4,m,m5], [m3,sp2[1][:],sp2[2][:]]]
  41. def cspbezsplitatlength(sp1, sp2, l = 0.5, tolerance = 0.001):
  42.     bez = (sp1[1][:],sp1[2][:],sp2[0][:],sp2[1][:])
  43.     t = bezmisc.beziertatlength(bez, l, tolerance)
  44.     return cspbezsplit(sp1, sp2, t)
  45. def cspseglength(sp1,sp2, tolerance = 0.001):
  46.     bez = (sp1[1][:],sp1[2][:],sp2[0][:],sp2[1][:])
  47.     return bezmisc.bezierlength(bez, tolerance)    
  48. def csplength(csp):
  49.     total = 0
  50.     lengths = []
  51.     for sp in csp:
  52.         lengths.append([])
  53.         for i in xrange(1,len(sp)):
  54.             l = cspseglength(sp[i-1],sp[i])
  55.             lengths[-1].append(l)
  56.             total += l            
  57.     return lengths, total
  58.     
  59. def tweenstylefloat(property, start, end, time):
  60.     sp = float(start[property])
  61.     ep = float(end[property])
  62.     return str(sp + (time * (ep - sp)))
  63. def tweenstyleunit(property, start, end, time):
  64.     sp = inkex.unittouu(start[property])
  65.     ep = inkex.unittouu(end[property])
  66.     return str(sp + (time * (ep - sp)))
  67. def tweenstylecolor(property, start, end, time):
  68.     sr,sg,sb = parsecolor(start[property])
  69.     er,eg,eb = parsecolor(end[property])
  70.     return '#%s%s%s' % (tweenhex(time,sr,er),tweenhex(time,sg,eg),tweenhex(time,sb,eb))
  71. def tweenhex(time,s,e):
  72.     s = float(int(s,16))
  73.     e = float(int(e,16))
  74.     retval = hex(int(math.floor(s + (time * (e - s)))))[2:]
  75.     if len(retval)==1:
  76.         retval = '0%s' % retval
  77.     return retval
  78. def parsecolor(c):
  79.     r,g,b = '0','0','0'
  80.     if c[:1]=='#':
  81.         if len(c)==4:
  82.             r,g,b = c[1:2],c[2:3],c[3:4]
  83.         elif len(c)==7:
  84.             r,g,b = c[1:3],c[3:5],c[5:7]
  85.     return r,g,b
  86.  
  87. class Interp(inkex.Effect):
  88.     def __init__(self):
  89.         inkex.Effect.__init__(self)
  90.         self.OptionParser.add_option("-e", "--exponent",
  91.                         action="store", type="float", 
  92.                         dest="exponent", default=0.0,
  93.                         help="values other than zero give non linear interpolation")
  94.         self.OptionParser.add_option("-s", "--steps",
  95.                         action="store", type="int", 
  96.                         dest="steps", default=5,
  97.                         help="number of interpolation steps")
  98.         self.OptionParser.add_option("-m", "--method",
  99.                         action="store", type="int", 
  100.                         dest="method", default=2,
  101.                         help="method of interpolation")
  102.         self.OptionParser.add_option("-d", "--dup",
  103.                         action="store", type="inkbool", 
  104.                         dest="dup", default=True,
  105.                         help="duplicate endpaths")    
  106.         self.OptionParser.add_option("--style",
  107.                         action="store", type="inkbool", 
  108.                         dest="style", default=True,
  109.                         help="try interpolation of some style properties")    
  110.     def effect(self):
  111.         exponent = self.options.exponent
  112.         if exponent>= 0:
  113.             exponent = 1.0 + exponent
  114.         else:
  115.             exponent = 1.0/(1.0 - exponent)
  116.         steps = [1.0/(self.options.steps + 1.0)]
  117.         for i in range(self.options.steps - 1):
  118.             steps.append(steps[0] + steps[-1])
  119.         steps = [step**exponent for step in steps]
  120.             
  121.         paths = {}            
  122.         styles = {}
  123.         for id in self.options.ids:
  124.             node = self.selected[id]
  125.             if node.tag ==inkex.addNS('path','svg'):
  126.                 paths[id] = cubicsuperpath.parsePath(node.get('d'))
  127.                 styles[id] = simplestyle.parseStyle(node.get('style'))
  128.                 trans = node.get('transform')
  129.                 if trans:
  130.                     simpletransform.applyTransformToPath(simpletransform.parseTransform(trans), paths[id])
  131.             else:
  132.                 self.options.ids.remove(id)
  133.  
  134.         for i in range(1,len(self.options.ids)):
  135.             start = copy.deepcopy(paths[self.options.ids[i-1]])
  136.             end = copy.deepcopy(paths[self.options.ids[i]])
  137.             sst = copy.deepcopy(styles[self.options.ids[i-1]])
  138.             est = copy.deepcopy(styles[self.options.ids[i]])
  139.             basestyle = copy.deepcopy(sst)
  140.  
  141.             #prepare for experimental style tweening
  142.             if self.options.style:
  143.                 dostroke = True
  144.                 dofill = True
  145.                 styledefaults = {'opacity':'1.0', 'stroke-opacity':'1.0', 'fill-opacity':'1.0',
  146.                         'stroke-width':'1.0', 'stroke':'none', 'fill':'none'}
  147.                 for key in styledefaults.keys():
  148.                     sst.setdefault(key,styledefaults[key])
  149.                     est.setdefault(key,styledefaults[key])
  150.                 isnotplain = lambda x: not (x=='none' or x[:1]=='#')
  151.                 if isnotplain(sst['stroke']) or isnotplain(est['stroke']) or (sst['stroke']=='none' and est['stroke']=='none'):
  152.                     dostroke = False
  153.                 if isnotplain(sst['fill']) or isnotplain(est['fill']) or (sst['fill']=='none' and est['fill']=='none'):
  154.                     dofill = False
  155.                 if dostroke:
  156.                     if sst['stroke']=='none':
  157.                         sst['stroke-width'] = '0.0'
  158.                         sst['stroke-opacity'] = '0.0'
  159.                         sst['stroke'] = est['stroke'] 
  160.                     elif est['stroke']=='none':
  161.                         est['stroke-width'] = '0.0'
  162.                         est['stroke-opacity'] = '0.0'
  163.                         est['stroke'] = sst['stroke'] 
  164.                 if dofill:
  165.                     if sst['fill']=='none':
  166.                         sst['fill-opacity'] = '0.0'
  167.                         sst['fill'] = est['fill'] 
  168.                     elif est['fill']=='none':
  169.                         est['fill-opacity'] = '0.0'
  170.                         est['fill'] = sst['fill'] 
  171.  
  172.                     
  173.  
  174.             if self.options.method == 2:
  175.                 #subdivide both paths into segments of relatively equal lengths
  176.                 slengths, stotal = csplength(start)
  177.                 elengths, etotal = csplength(end)
  178.                 lengths = {}
  179.                 t = 0
  180.                 for sp in slengths:
  181.                     for l in sp:
  182.                         t += l / stotal
  183.                         lengths.setdefault(t,0)
  184.                         lengths[t] += 1
  185.                 t = 0
  186.                 for sp in elengths:
  187.                     for l in sp:
  188.                         t += l / etotal
  189.                         lengths.setdefault(t,0)
  190.                         lengths[t] += -1
  191.                 sadd = [k for (k,v) in lengths.iteritems() if v < 0]
  192.                 sadd.sort()
  193.                 eadd = [k for (k,v) in lengths.iteritems() if v > 0]
  194.                 eadd.sort()
  195.  
  196.                 t = 0
  197.                 s = [[]]
  198.                 for sp in slengths:
  199.                     if not start[0]:
  200.                         s.append(start.pop(0))
  201.                     s[-1].append(start[0].pop(0))
  202.                     for l in sp:
  203.                         pt = t
  204.                         t += l / stotal
  205.                         if sadd and t > sadd[0]:
  206.                             while sadd and sadd[0] < t:
  207.                                 nt = (sadd[0] - pt) / (t - pt)
  208.                                 bezes = cspbezsplitatlength(s[-1][-1][:],start[0][0][:], nt)
  209.                                 s[-1][-1:] = bezes[:2]
  210.                                 start[0][0] = bezes[2]
  211.                                 pt = sadd.pop(0)
  212.                         s[-1].append(start[0].pop(0))
  213.                 t = 0
  214.                 e = [[]]
  215.                 for sp in elengths:
  216.                     if not end[0]:
  217.                         e.append(end.pop(0))
  218.                     e[-1].append(end[0].pop(0))
  219.                     for l in sp:
  220.                         pt = t
  221.                         t += l / etotal
  222.                         if eadd and t > eadd[0]:
  223.                             while eadd and eadd[0] < t:
  224.                                 nt = (eadd[0] - pt) / (t - pt)
  225.                                 bezes = cspbezsplitatlength(e[-1][-1][:],end[0][0][:], nt)
  226.                                 e[-1][-1:] = bezes[:2]
  227.                                 end[0][0] = bezes[2]
  228.                                 pt = eadd.pop(0)
  229.                         e[-1].append(end[0].pop(0))
  230.                 start = s[:]
  231.                 end = e[:]
  232.             else:
  233.                 #which path has fewer segments?
  234.                 lengthdiff = numsegs(start) - numsegs(end)
  235.                 #swap shortest first
  236.                 if lengthdiff > 0:
  237.                     start, end = end, start
  238.                 #subdivide the shorter path
  239.                 for x in range(abs(lengthdiff)):
  240.                     maxlen = 0
  241.                     subpath = 0
  242.                     segment = 0
  243.                     for y in range(len(start)):
  244.                         for z in range(1, len(start[y])):
  245.                             leng = bezlenapprx(start[y][z-1], start[y][z])
  246.                             if leng > maxlen:
  247.                                 maxlen = leng
  248.                                 subpath = y
  249.                                 segment = z
  250.                     sp1, sp2 = start[subpath][segment - 1:segment + 1]
  251.                     start[subpath][segment - 1:segment + 1] = cspbezsplit(sp1, sp2)
  252.                 #if swapped, swap them back
  253.                 if lengthdiff > 0:
  254.                     start, end = end, start
  255.             
  256.             #break paths so that corresponding subpaths have an equal number of segments
  257.             s = [[]]
  258.             e = [[]]
  259.             while start and end:
  260.                 if start[0] and end[0]:
  261.                     s[-1].append(start[0].pop(0))
  262.                     e[-1].append(end[0].pop(0))
  263.                 elif end[0]:
  264.                     s.append(start.pop(0))
  265.                     e[-1].append(end[0][0])
  266.                     e.append([end[0].pop(0)])
  267.                 elif start[0]:
  268.                     e.append(end.pop(0))
  269.                     s[-1].append(start[0][0])
  270.                     s.append([start[0].pop(0)])
  271.                 else:
  272.                     s.append(start.pop(0))
  273.                     e.append(end.pop(0))
  274.     
  275.             if self.options.dup:
  276.                 steps = [0] + steps + [1]    
  277.             #create an interpolated path for each interval
  278.             group = inkex.etree.SubElement(self.current_layer,inkex.addNS('g','svg'))    
  279.             for time in steps:
  280.                 interp = []
  281.                 #process subpaths
  282.                 for ssp,esp in zip(s, e):
  283.                     if not (ssp or esp):
  284.                         break
  285.                     interp.append([])
  286.                     #process superpoints
  287.                     for sp,ep in zip(ssp, esp):
  288.                         if not (sp or ep):
  289.                             break
  290.                         interp[-1].append([])
  291.                         #process points
  292.                         for p1,p2 in zip(sp, ep):
  293.                             if not (sp or ep):
  294.                                 break
  295.                             interp[-1][-1].append(interppoints(p1, p2, time))
  296.  
  297.                 #remove final subpath if empty.
  298.                 if not interp[-1]:
  299.                     del interp[-1]
  300.  
  301.                 #basic style tweening
  302.                 if self.options.style:
  303.                     basestyle['opacity'] = tweenstylefloat('opacity',sst,est,time)
  304.                     if dostroke:
  305.                         basestyle['stroke-opacity'] = tweenstylefloat('stroke-opacity',sst,est,time)
  306.                         basestyle['stroke-width'] = tweenstyleunit('stroke-width',sst,est,time)
  307.                         basestyle['stroke'] = tweenstylecolor('stroke',sst,est,time)
  308.                     if dofill:
  309.                         basestyle['fill-opacity'] = tweenstylefloat('fill-opacity',sst,est,time)
  310.                         basestyle['fill'] = tweenstylecolor('fill',sst,est,time)
  311.                 attribs = {'style':simplestyle.formatStyle(basestyle),'d':cubicsuperpath.formatPath(interp)}
  312.                 new = inkex.etree.SubElement(group,inkex.addNS('path','svg'), attribs)
  313.  
  314. if __name__ == '__main__':
  315.     e = Interp()
  316.     e.affect()
  317.  
  318.  
  319. # vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 encoding=utf-8 textwidth=99
  320.