interpolatableTestStartingPoint.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. from .interpolatableHelpers import *
  2. def test_starting_point(glyph0, glyph1, ix, tolerance, matching):
  3. if matching is None:
  4. matching = list(range(len(glyph0.isomorphisms)))
  5. contour0 = glyph0.isomorphisms[ix]
  6. contour1 = glyph1.isomorphisms[matching[ix]]
  7. m0Vectors = glyph0.greenVectors
  8. m1Vectors = [glyph1.greenVectors[i] for i in matching]
  9. c0 = contour0[0]
  10. # Next few lines duplicated below.
  11. costs = [vdiff_hypot2_complex(c0[0], c1[0]) for c1 in contour1]
  12. min_cost_idx, min_cost = min(enumerate(costs), key=lambda x: x[1])
  13. first_cost = costs[0]
  14. proposed_point = contour1[min_cost_idx][1]
  15. reverse = contour1[min_cost_idx][2]
  16. if min_cost < first_cost * tolerance:
  17. # c0 is the first isomorphism of the m0 master
  18. # contour1 is list of all isomorphisms of the m1 master
  19. #
  20. # If the two shapes are both circle-ish and slightly
  21. # rotated, we detect wrong start point. This is for
  22. # example the case hundreds of times in
  23. # RobotoSerif-Italic[GRAD,opsz,wdth,wght].ttf
  24. #
  25. # If the proposed point is only one off from the first
  26. # point (and not reversed), try harder:
  27. #
  28. # Find the major eigenvector of the covariance matrix,
  29. # and rotate the contours by that angle. Then find the
  30. # closest point again. If it matches this time, let it
  31. # pass.
  32. num_points = len(glyph1.points[ix])
  33. leeway = 3
  34. if not reverse and (
  35. proposed_point <= leeway or proposed_point >= num_points - leeway
  36. ):
  37. # Try harder
  38. # Recover the covariance matrix from the GreenVectors.
  39. # This is a 2x2 matrix.
  40. transforms = []
  41. for vector in (m0Vectors[ix], m1Vectors[ix]):
  42. meanX = vector[1]
  43. meanY = vector[2]
  44. stddevX = vector[3] * 0.5
  45. stddevY = vector[4] * 0.5
  46. correlation = vector[5]
  47. if correlation:
  48. correlation /= abs(vector[0])
  49. # https://cookierobotics.com/007/
  50. a = stddevX * stddevX # VarianceX
  51. c = stddevY * stddevY # VarianceY
  52. b = correlation * stddevX * stddevY # Covariance
  53. delta = (((a - c) * 0.5) ** 2 + b * b) ** 0.5
  54. lambda1 = (a + c) * 0.5 + delta # Major eigenvalue
  55. lambda2 = (a + c) * 0.5 - delta # Minor eigenvalue
  56. theta = atan2(lambda1 - a, b) if b != 0 else (pi * 0.5 if a < c else 0)
  57. trans = Transform()
  58. # Don't translate here. We are working on the complex-vector
  59. # that includes more than just the points. It's horrible what
  60. # we are doing anyway...
  61. # trans = trans.translate(meanX, meanY)
  62. trans = trans.rotate(theta)
  63. trans = trans.scale(sqrt(lambda1), sqrt(lambda2))
  64. transforms.append(trans)
  65. trans = transforms[0]
  66. new_c0 = (
  67. [complex(*trans.transformPoint((pt.real, pt.imag))) for pt in c0[0]],
  68. ) + c0[1:]
  69. trans = transforms[1]
  70. new_contour1 = []
  71. for c1 in contour1:
  72. new_c1 = (
  73. [
  74. complex(*trans.transformPoint((pt.real, pt.imag)))
  75. for pt in c1[0]
  76. ],
  77. ) + c1[1:]
  78. new_contour1.append(new_c1)
  79. # Next few lines duplicate from above.
  80. costs = [
  81. vdiff_hypot2_complex(new_c0[0], new_c1[0]) for new_c1 in new_contour1
  82. ]
  83. min_cost_idx, min_cost = min(enumerate(costs), key=lambda x: x[1])
  84. first_cost = costs[0]
  85. if min_cost < first_cost * tolerance:
  86. # Don't report this
  87. # min_cost = first_cost
  88. # reverse = False
  89. # proposed_point = 0 # new_contour1[min_cost_idx][1]
  90. pass
  91. this_tolerance = min_cost / first_cost if first_cost else 1
  92. log.debug(
  93. "test-starting-point: tolerance %g",
  94. this_tolerance,
  95. )
  96. return this_tolerance, proposed_point, reverse