This repository has been archived on 2023-03-25. You can view files and clone it, but cannot push or open issues or pull requests.

305 lines
11 KiB
Python
Raw Normal View History

import numpy
import sys
import collections
numpy.set_printoptions(precision=3)
# *************************************************************
# debugging
def void(*l):
pass
def debug_on(*l):
sys.stderr.write(' '.join(str(i) for i in l) +'\n')
debug = void
#debug = debug_on
curveFragments = 10
def qudSmRelBezCurFrag(ctrlPts, startPoint):
#just call the normal one with adjusted coordinates
ctrlPts[0] = startPoint[-2] + ctrlPts[0]
ctrlPts[1] = startPoint[-1] + ctrlPts[1]
return qudSmBezCurFrag(ctrlPts, startPoint)
def qudSmBezCurFrag(ctrlPts, startPoint):
#There are no control points
debug("startPoint: '", startPoint, "'")
debug("shouldbethehandle: '", [startPoint[2] + (startPoint[2] - startPoint[0]), startPoint[3] + (startPoint[3] - startPoint[1])], "'")
#ctrlPtsExt = [startPoint[2] + (startPoint[2] - startPoint[0]), startPoint[3] + (startPoint[3] - startPoint[1])] + ctrlPts
ctrlPtsExt = [startPoint[-2] + (startPoint[-2] - startPoint[-4]), startPoint[-1] + (startPoint[-1] - startPoint[-3])] + ctrlPts
debug("startPoint: '", startPoint, "'")
debug("ctrlPts: '", ctrlPts, "'")
debug("startPoint[-2:]: '", startPoint[-2:], "'")
debug("ctrlPtsExt: '", ctrlPtsExt, "'")
debug("shound be the same as non smooth: '", ctrlPtsExt)
return cubBezCurFrag(ctrlPtsExt, startPoint[-2:])
def qudRelBezCurFrag(ctrlPts, startPoint):
#just call the normal one with adjusted coordinates
ctrlPts[0] = startPoint[-2] + ctrlPts[0]
ctrlPts[1] = startPoint[-1] + ctrlPts[1]
return qudBezCurFrag(ctrlPts, startPoint)
def qudBezCurFrag(ctrlPts, startPoint):
#tested working
debug("startPoint: '", startPoint, "'")
return cubBezCurFrag(ctrlPts, startPoint[-2:])
def cubSmRelBezCurFrag(ctrlPts, startPoint):
#just call the normal one with adjusted coordinates
#[prevL[-1][0] + x[0:1][0] , prevL[1] + x[1:2][1]]
ctrlPts[0] = startPoint[-2] + ctrlPts[0]
ctrlPts[1] = startPoint[-1] + ctrlPts[1]
return cubSmBezCurFrag(ctrlPts, startPoint)
def cubSmBezCurFrag(ctrlPts, startPoint):
#tested working
#just call the normal one with adjusted coordinates
ctrlPtsExt = [startPoint[-2] + (startPoint[-2] - startPoint[-4]), startPoint[-1] + (startPoint[-1] - startPoint[-3])] + ctrlPts
return cubBezCurFrag(ctrlPtsExt, startPoint[-2:])
def cubRelBezCurFrag(ctrlPts, startPoint):
#just call the normal one with adjusted coordinates
ctrlPts[0] = startPoint[-2] + ctrlPts[0]
ctrlPts[1] = startPoint[-1] + ctrlPts[1]
return cubBezCurFrag(ctrlPts, startPoint)
def compute(t, points): #, _3d
#// shortcuts
if (t == 0) :
#points[0].t = 0;
return points[0:2]
order = int((int(len(points)) / 2)) - 1;
if (t == 1) :
#points[order].t = 1;
return points[order * 2:order * 2 + 2]
mt = 1 - t
p = points
# // constant?
if (order == 0):
#points[0].t = t;
return points[0:2]
#// linear?
if (order == 1):
ret = [
mt * p[0] + t * p[2],
mt * p[1] + t * p[3]
# t: t,
]
# if (_3d) {
# ret.z = mt * p[0].z + t * p[1].z;
# }
return ret
#// quadratic/cubic curve?
mt2 = 0
a = b = c = d = 0
if (order < 4):
mt2 = mt * mt
t2 = t * t
else:
sys.stderr.write("Order :'" + str(order) +"' beyond limits of function")
return [0.0, 0.0]
if (order == 2):
p = p + [0,0]
a = mt2
b = mt * t * 2
c = t2
elif (order == 3):
a = mt2 * mt
b = mt2 * t * 3
c = mt * t2 * 3
d = t * t2
ret = [
a * p[0] + b * p[2] + c * p[4] + d * p[6],
a * p[1] + b * p[3] + c * p[5] + d * p[7]
]
# if (_3d) {
# ret.z = a * p[0].z + b * p[1].z + c * p[2].z + d * p[3].z;
# }
return ret
#TODO: Number of fragments should possibly be based upon the length of the segment.
def cubBezCurFrag(ctrlPts, startPoint):
#tested working
points =[]
rng = range(0, curveFragments + 1)
#sx = startPoint[0]
#sy = startPoint[1]
debug("control ", startPoint + ctrlPts)
inputPoints = startPoint + ctrlPts
for i in rng:
t = (float(i) / float(len(rng) - 1))
debug("t ", t, ":", i)
newp = compute(t, inputPoints)
#newp= [(1-t) ** 3 * startPoint[0] + 3 * ((1-t) ** 2) * t* ctrlPts[0] +3 * (1-t) * (t ** 2) * ctrlPts[2] + (t ** 3) * ctrlPts[4],
#(1-t) ** 3 * startPoint[1] + 3 * ((1-t) ** 2) * t* ctrlPts[1] +3 * (1-t) * (t ** 2) * ctrlPts[3] + (t ** 3) * ctrlPts[5]]
points.append(newp)
debug("result ", points)
return points
# *************************************************************
# a list of geometric helper functions
def toArray(parsedList):
"""Interprets a list of [(command, args),...]
where command is a letter coding for a svg path command
args are the argument of the command
"""
# The set of commands is now complete, all absolute positioning has been tested, relative positioning still neds some more testing.
# Curved parts of the path need fragmenting instead of just being taken as a straight line.
interpretCommand = {
'C': lambda x, prevL : cubBezCurFrag(x, prevL[-2:]), # cubic bezier curve. Ignore the curve. #TODO, fragment
'c': lambda x, prevL : cubRelBezCurFrag(x, prevL[-2:]), # cubic bezier curve, relative. Ignore the curve. #TODO, fragment
'S': lambda x, prevL : cubSmBezCurFrag(x, prevL), # cubic bezier curve, smooth. TODO, fragment
's': lambda x, prevL : cubSmRelBezCurFrag(x, prevL), # cubic bezier curve, smooth, relative. TODO, fragment
'Q': lambda x, prevL : qudBezCurFrag(x, prevL), # quadratic bezier curve. TODO, fragment
'q': lambda x, prevL : qudRelBezCurFrag(x, prevL), # quadratic bezier curve, relative TODO, fragment
#[[prevL[-1][0] + x[0:1][0] , prevL[1] + x[1:2][1]]], # quadratic bezier curve, relative TODO, fragment
'T': lambda x, prevL : qudSmBezCurFrag(x, prevL), # quadratic bezier curve, smooth. TODO, fragment
't': lambda x, prevL : qudSmRelBezCurFrag(x, prevL), # quadratic bezier curve, smooth, relative. TODO, fragment
'L': lambda x, prevL : [x[0:2]], # Line
'l': lambda x, prevL : [[prevL[0] + x[0:1][0] , prevL[1] + x[1:2][1]]], # Line, relative
'M': lambda x, prevL : [x[0:2]], # Move
'm': lambda x, prevL : [[prevL[0] + x[0:1][0] , prevL[1] + x[1:2][1]]], # Move, Relative
'H': lambda x, prevL : [[x[0:1][0],prevL[1]]], # Horizontal
'h': lambda x, prevL : [[prevL[0] + x[0:1][0], prevL[1]]], # Horizontal , relative
'V': lambda x, prevL : [[prevL[0], x[0:1][0]]], # Verticle
'v': lambda x, prevL : [[prevL[0], prevL[1] + x[0:1][0]]], # Verticle, relative
'A': lambda x, prevL : [x[5:7]], # Arc segment
'a': lambda x, prevL : [[prevL[0] + x[5:6][0] , prevL[1] + x[6:7][1]]], # Arc segment, relative
'Z': lambda x, prevL : [prevL[0]], # Close path
'z': lambda x, prevL : [prevL[0]], # Close path
}
#append the last set of attributes of the first element to the lookBack for cases where smooth or smooth quad segments appear at the begining of a path.
lookBack = parsedList[0][1] + parsedList[0][1]
points =[]
for i, (c, arg) in enumerate(parsedList):
debug('toArray ', i, c , arg)
debug('lookBack: ', lookBack)
newp = interpretCommand[c](arg, lookBack)
#double up if there are only two point entries to support transition into smooth beziers or arrays of smooth beziers etc..
if len(arg) == 2:
arg = arg + arg
#we only need to keep the last element in the lookBack, so remove any elemenmts in front.
lookBack = arg
debug('newPoints ', newp)
points = points + newp
a = numpy.array(points, dtype="object")
# Some times we have points *very* close to each other
# these do not bring any meaning full info, so we remove them
#
x, y, w, h = computeBox(a)
sizeC = 0.5*(w+h)
#deltas = numpy.zeros((len(a),2) )
deltas = a[1:] - a[:-1]
#deltas[-1] = a[0] - a[-1]
deltaD = numpy.sqrt(numpy.sum( deltas**2, 1 ))
sortedDind = numpy.argsort(deltaD)
# # expand longuest segments
nexp = int(len(deltaD)*0.9)
newpoints=[ None ]*len(a)
medDelta = deltaD[sortedDind[int(len(deltaD)/2)] ]
for i, ind in enumerate(sortedDind):
if deltaD[ind]/sizeC<0.005: continue
if i>nexp:
np = int(deltaD[ind]/medDelta)
pL = [a[ind]]
#print i,'=',ind,'adding ', np,' _ ', deltaD[ind], a[ind], a[ind+1]
for j in range(np-1):
f = float(j+1)/np
#print '------> ', (1-f)*a[ind]+f*a[ind+1]
pL.append( (1-f)*a[ind]+f*a[ind+1] )
newpoints[ind] = pL
else:
newpoints[ind]=[a[ind]]
if(D(a[0], a[-1])/sizeC > 0.005 ) :
newpoints[-1]=[a[-1]]
points = numpy.concatenate([p for p in newpoints if p!=None] )
# ## print ' medDelta ', medDelta, deltaD[sortedDind[-1]]
# ## print len(a) ,' ------> ', len(points)
rel_norms = numpy.sqrt(numpy.sum( deltas**2, 1 )) / sizeC
keep = numpy.concatenate([numpy.where( rel_norms >0.005 )[0], numpy.array([len(a)-1])])
#return a[keep] , [ parsedList[i] for i in keep]
#print len(a),' ',len(points)
return points, []
rotMat = numpy.array( [[1, -1], [1, 1]] )/numpy.sqrt(2)
unrotMat = numpy.array( [[1, 1], [-1, 1]] )/numpy.sqrt(2)
def setupKnownAngles():
pi = numpy.pi
#l = [ i*pi/8 for i in range(0, 9)] +[ i*pi/6 for i in [1,2,4,5,] ]
l = [ i*pi/8 for i in range(0, 9)] +[ i*pi/6 for i in [1, 2, 4, 5,] ] + [i*pi/12 for i in (1, 5, 7, 11)]
knownAngle = numpy.array( l )
return numpy.concatenate( [-knownAngle[:0:-1], knownAngle ])
knownAngle = setupKnownAngles()
_twopi = 2*numpy.pi
_pi = numpy.pi
def deltaAngle(a1, a2):
d = a1 - a2
return d if d > -_pi else d+_twopi
def closeAngleAbs(a1, a2):
d = abs(a1 - a2 )
return min( abs(d-_pi), abs( _twopi - d), d)
def deltaAngleAbs(a1, a2):
return abs(in_mPi_pPi(a1 - a2 ))
def in_mPi_pPi(a):
if(a>_pi): return a-_twopi
if(a<-_pi): return a+_twopi
return a
vec_in_mPi_pPi = numpy.vectorize(in_mPi_pPi)
def D2(p1, p2):
return ((p1-p2)**2).sum()
def D(p1, p2):
return numpy.sqrt(D2(p1, p2) )
def norm(p):
return numpy.sqrt( (p**2).sum() )
def computeBox(a):
"""returns the bounding box enclosing the array of points a
in the form (x,y, width, height) """
xmin, ymin = a[:, 0].min(), a[:, 1].min()
xmax, ymax = a[:, 0].max(), a[:, 1].max()
return xmin, ymin, xmax-xmin, ymax-ymin
def dirAndLength(p1, p2):
#l = max(D(p1, p2) ,1e-4)
l = D(p1, p2)
uv = (p1-p2)/l
return l, uv
def length(p1, p2):
return numpy.sqrt( D2(p1, p2) )
def barycenter(points):
"""
"""
return points.sum(axis=0)/len(points)