Code

Translations. French translation minor update.
[inkscape.git] / share / extensions / interp.py
1 #!/usr/bin/env python 
2 '''
3 Copyright (C) 2005 Aaron Spike, aaron@ekips.org
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.
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.
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
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
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             else:
129                 self.options.ids.remove(id)
131         for i in range(1,len(self.options.ids)):
132             start = copy.deepcopy(paths[self.options.ids[i-1]])
133             end = copy.deepcopy(paths[self.options.ids[i]])
134             sst = copy.deepcopy(styles[self.options.ids[i-1]])
135             est = copy.deepcopy(styles[self.options.ids[i]])
136             basestyle = copy.deepcopy(sst)
138             #prepare for experimental style tweening
139             if self.options.style:
140                 dostroke = True
141                 dofill = True
142                 styledefaults = {'opacity':'1.0', 'stroke-opacity':'1.0', 'fill-opacity':'1.0',
143                         'stroke-width':'1.0', 'stroke':'none', 'fill':'none'}
144                 for key in styledefaults.keys():
145                     sst.setdefault(key,styledefaults[key])
146                     est.setdefault(key,styledefaults[key])
147                 isnotplain = lambda x: not (x=='none' or x[:1]=='#')
148                 if isnotplain(sst['stroke']) or isnotplain(est['stroke']) or (sst['stroke']=='none' and est['stroke']=='none'):
149                     dostroke = False
150                 if isnotplain(sst['fill']) or isnotplain(est['fill']) or (sst['fill']=='none' and est['fill']=='none'):
151                     dofill = False
152                 if dostroke:
153                     if sst['stroke']=='none':
154                         sst['stroke-width'] = '0.0'
155                         sst['stroke-opacity'] = '0.0'
156                         sst['stroke'] = est['stroke'] 
157                     elif est['stroke']=='none':
158                         est['stroke-width'] = '0.0'
159                         est['stroke-opacity'] = '0.0'
160                         est['stroke'] = sst['stroke'] 
161                 if dofill:
162                     if sst['fill']=='none':
163                         sst['fill-opacity'] = '0.0'
164                         sst['fill'] = est['fill'] 
165                     elif est['fill']=='none':
166                         est['fill-opacity'] = '0.0'
167                         est['fill'] = sst['fill'] 
169                     
171             if self.options.method == 2:
172                 #subdivide both paths into segments of relatively equal lengths
173                 slengths, stotal = csplength(start)
174                 elengths, etotal = csplength(end)
175                 lengths = {}
176                 t = 0
177                 for sp in slengths:
178                     for l in sp:
179                         t += l / stotal
180                         lengths.setdefault(t,0)
181                         lengths[t] += 1
182                 t = 0
183                 for sp in elengths:
184                     for l in sp:
185                         t += l / etotal
186                         lengths.setdefault(t,0)
187                         lengths[t] += -1
188                 sadd = [k for (k,v) in lengths.iteritems() if v < 0]
189                 sadd.sort()
190                 eadd = [k for (k,v) in lengths.iteritems() if v > 0]
191                 eadd.sort()
193                 t = 0
194                 s = [[]]
195                 for sp in slengths:
196                     if not start[0]:
197                         s.append(start.pop(0))
198                     s[-1].append(start[0].pop(0))
199                     for l in sp:
200                         pt = t
201                         t += l / stotal
202                         if sadd and t > sadd[0]:
203                             while sadd and sadd[0] < t:
204                                 nt = (sadd[0] - pt) / (t - pt)
205                                 bezes = cspbezsplitatlength(s[-1][-1][:],start[0][0][:], nt)
206                                 s[-1][-1:] = bezes[:2]
207                                 start[0][0] = bezes[2]
208                                 pt = sadd.pop(0)
209                         s[-1].append(start[0].pop(0))
210                 t = 0
211                 e = [[]]
212                 for sp in elengths:
213                     if not end[0]:
214                         e.append(end.pop(0))
215                     e[-1].append(end[0].pop(0))
216                     for l in sp:
217                         pt = t
218                         t += l / etotal
219                         if eadd and t > eadd[0]:
220                             while eadd and eadd[0] < t:
221                                 nt = (eadd[0] - pt) / (t - pt)
222                                 bezes = cspbezsplitatlength(e[-1][-1][:],end[0][0][:], nt)
223                                 e[-1][-1:] = bezes[:2]
224                                 end[0][0] = bezes[2]
225                                 pt = eadd.pop(0)
226                         e[-1].append(end[0].pop(0))
227                 start = s[:]
228                 end = e[:]
229             else:
230                 #which path has fewer segments?
231                 lengthdiff = numsegs(start) - numsegs(end)
232                 #swap shortest first
233                 if lengthdiff > 0:
234                     start, end = end, start
235                 #subdivide the shorter path
236                 for x in range(abs(lengthdiff)):
237                     maxlen = 0
238                     subpath = 0
239                     segment = 0
240                     for y in range(len(start)):
241                         for z in range(1, len(start[y])):
242                             leng = bezlenapprx(start[y][z-1], start[y][z])
243                             if leng > maxlen:
244                                 maxlen = leng
245                                 subpath = y
246                                 segment = z
247                     sp1, sp2 = start[subpath][segment - 1:segment + 1]
248                     start[subpath][segment - 1:segment + 1] = cspbezsplit(sp1, sp2)
249                 #if swapped, swap them back
250                 if lengthdiff > 0:
251                     start, end = end, start
252             
253             #break paths so that corresponding subpaths have an equal number of segments
254             s = [[]]
255             e = [[]]
256             while start and end:
257                 if start[0] and end[0]:
258                     s[-1].append(start[0].pop(0))
259                     e[-1].append(end[0].pop(0))
260                 elif end[0]:
261                     s.append(start.pop(0))
262                     e[-1].append(end[0][0])
263                     e.append([end[0].pop(0)])
264                 elif start[0]:
265                     e.append(end.pop(0))
266                     s[-1].append(start[0][0])
267                     s.append([start[0].pop(0)])
268                 else:
269                     s.append(start.pop(0))
270                     e.append(end.pop(0))
271     
272             if self.options.dup:
273                 steps = [0] + steps + [1]    
274             #create an interpolated path for each interval
275             group = inkex.etree.SubElement(self.current_layer,inkex.addNS('g','svg'))    
276             for time in steps:
277                 interp = []
278                 #process subpaths
279                 for ssp,esp in zip(s, e):
280                     if not (ssp or esp):
281                         break
282                     interp.append([])
283                     #process superpoints
284                     for sp,ep in zip(ssp, esp):
285                         if not (sp or ep):
286                             break
287                         interp[-1].append([])
288                         #process points
289                         for p1,p2 in zip(sp, ep):
290                             if not (sp or ep):
291                                 break
292                             interp[-1][-1].append(interppoints(p1, p2, time))
294                 #remove final subpath if empty.
295                 if not interp[-1]:
296                     del interp[-1]
298                 #basic style tweening
299                 if self.options.style:
300                     basestyle['opacity'] = tweenstylefloat('opacity',sst,est,time)
301                     if dostroke:
302                         basestyle['stroke-opacity'] = tweenstylefloat('stroke-opacity',sst,est,time)
303                         basestyle['stroke-width'] = tweenstyleunit('stroke-width',sst,est,time)
304                         basestyle['stroke'] = tweenstylecolor('stroke',sst,est,time)
305                     if dofill:
306                         basestyle['fill-opacity'] = tweenstylefloat('fill-opacity',sst,est,time)
307                         basestyle['fill'] = tweenstylecolor('fill',sst,est,time)
308                 attribs = {'style':simplestyle.formatStyle(basestyle),'d':cubicsuperpath.formatPath(interp)}
309                 new = inkex.etree.SubElement(group,inkex.addNS('path','svg'), attribs)
311 if __name__ == '__main__':
312     e = Interp()
313     e.affect()
316 # vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 fileencoding=utf-8 textwidth=99