X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=share%2Fextensions%2Finterp.py;h=ba8d8d7f40f6ec8b2e1494c5f4cc871b2704bc09;hb=b8479bf7d4f211f77d10c09ae1829bd2d1bfa524;hp=5edbf026ddc34366f4b706d83c199194dfd72ae5;hpb=6b15695578f07a3f72c4c9475c1a261a3021472a;p=inkscape.git diff --git a/share/extensions/interp.py b/share/extensions/interp.py index 5edbf026d..ba8d8d7f4 100755 --- a/share/extensions/interp.py +++ b/share/extensions/interp.py @@ -20,313 +20,313 @@ 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]) + return sum([len(p)-1 for p in csp]) def interpcoord(v1,v2,p): - return v1+((v2-v1)*p) + return v1+((v2-v1)*p) def interppoints(p1,p2,p): - return [interpcoord(p1[0],p2[0],p),interpcoord(p1[1],p2[1],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)) + 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]) + 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)] + 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][:]]] + 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) + 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) + 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 + 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]+)?)') + 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 - + 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))) + 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))) + 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)) + 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 + 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 + 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) + 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) + 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'] + #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() + 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)) + 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.current_layer.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') + #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) + #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()