mirror of
				https://github.com/skishore/makemeahanzi.git
				synced 2025-11-04 06:32:58 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			92 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			92 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
import math
 | 
						|
 | 
						|
def augment_glyph(strokes):
 | 
						|
  '''
 | 
						|
  Takes a list of svg.path.Path objects that represent strokes and returns
 | 
						|
  additional SVG elements that roughly correspond to a "median", a line that
 | 
						|
  runs down the middle of the stroke.
 | 
						|
  '''
 | 
						|
  polygons = []
 | 
						|
  medians = []
 | 
						|
  for path in strokes:
 | 
						|
    polygons.append(get_polygon_approximation(path, 32))
 | 
						|
    medians.append(find_median(polygons[-1], 128))
 | 
						|
  result = []
 | 
						|
  for polygon in polygons:
 | 
						|
    for point in polygon:
 | 
						|
      result.append(
 | 
						|
          '<circle cx="{0}" cy="{1}" r="4" fill="red" stroke="red"/>'.format(
 | 
						|
              int(point.real), int(point.imag)))
 | 
						|
  for median in medians:
 | 
						|
    for point in median:
 | 
						|
      result.append(
 | 
						|
          '<circle cx="{0}" cy="{1}" r="4" fill="{2}" stroke="{2}"/>'.format(
 | 
						|
              int(point.real), int(point.imag), 'black'))
 | 
						|
  return result
 | 
						|
 | 
						|
 | 
						|
def find_median(polygon, max_distance):
 | 
						|
  result = []
 | 
						|
  for i, point2 in enumerate(polygon):
 | 
						|
    # For each polygon edge, we compute its midpoint and consider the portion
 | 
						|
    # of its perpendicular bisector that extends into the polygon. We prepare
 | 
						|
    # a few functions to compute dot products against this bisector:
 | 
						|
    #   - dot measures which side of the bisector points are on. Note that
 | 
						|
    #     dot(point) - dotmid is 0 if the point is on the perpendicular
 | 
						|
    #     bisector, negative if it is on one side, and positive on the other.
 | 
						|
    #   - sid measures whether the point is inside our outside the polygon.
 | 
						|
    #     sid(point) - sidmid is 0 if the point is on this segment, positive
 | 
						|
    #     if the point is within the polygon, and negative if it is outside.
 | 
						|
    point1 = polygon[i - 1]
 | 
						|
    midpoint = (point1 + point2)/2
 | 
						|
    diff = point2 - point1
 | 
						|
    dot = lambda point: diff.real*point.real + diff.imag*point.imag
 | 
						|
    sid = lambda point: -diff.imag*point.real + diff.real*point.imag
 | 
						|
    dotmid = dot(midpoint)
 | 
						|
    sidmid = sid(midpoint)
 | 
						|
    # For each other segment, we compute its intersection with the perpendicular
 | 
						|
    # bisector and track the closest one overall.
 | 
						|
    (best, best_distance, best_tangent) = (None, float('Inf'), None)
 | 
						|
    for j, other2 in enumerate(polygon):
 | 
						|
      if j == i:
 | 
						|
        continue
 | 
						|
      other1 = polygon[j - 1]
 | 
						|
      (dot1, dot2) = (dot(other1) - dotmid, dot(other2) - dotmid)
 | 
						|
      if dot1 == dot2 == 0:
 | 
						|
        if abs(other1 - diff) > abs(other2 - diff):
 | 
						|
          (other1, other2) = (other2, other1)
 | 
						|
        intersection = other1 if dot1 == 0 else other2
 | 
						|
      elif cmp(dot1, 0) == cmp(dot2, 0):
 | 
						|
        continue
 | 
						|
      else:
 | 
						|
        t = dot1/(dot1 - dot2)
 | 
						|
        intersection = (1 - t)*other1 + t*other2
 | 
						|
      distance = abs(intersection - midpoint)
 | 
						|
      if sid(intersection) > sidmid and distance < best_distance:
 | 
						|
        tangent = other2 - other1
 | 
						|
        (best, best_distance, best_tangent) = (intersection, distance, tangent)
 | 
						|
    # If the perpendicular bisector intersects a segment opposite this one in
 | 
						|
    # the polygon, we compute a point between the midpoint and the intersection
 | 
						|
    # point as a candidate for our median line.
 | 
						|
    #
 | 
						|
    # We do NOT take (midpoint + best)/2. If this segment is not parallel to the
 | 
						|
    # opposite segment, that point could be far from the median. Instead, we
 | 
						|
    # compute the angle bisector of this segment and the opposte one and find
 | 
						|
    # its intersection with the segment (midpoint, best).
 | 
						|
    if best is None or best_distance > max_distance or not diff:
 | 
						|
      continue
 | 
						|
    ratio = best_tangent/diff
 | 
						|
    cosine = abs(math.cos(math.atan2(ratio.imag, ratio.real)))
 | 
						|
    t = cosine/(1 + cosine)
 | 
						|
    result.append((1 - t)*midpoint + t*best)
 | 
						|
  return result
 | 
						|
 | 
						|
 | 
						|
def get_polygon_approximation(path, error):
 | 
						|
  result = []
 | 
						|
  for i, element in enumerate(path):
 | 
						|
    num_interpolating_points = max(int(element.length()/error), 1)
 | 
						|
    for i in xrange(num_interpolating_points):
 | 
						|
      result.append(element.point(1.0*i/num_interpolating_points))
 | 
						|
  return result
 |