2021-07-23 02:36:56 +02:00
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
2021-10-13 17:13:24 +02:00
a = numpy . array ( points )
2021-07-23 02:36:56 +02:00
# 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 )