Coverage for paintmaskanimator/geometry.py: 95%

35 statements  

« prev     ^ index     » next       coverage.py v7.17.0, created at 2026-11-02 04:50 +0000

1"""Pure geometry / math helpers extracted from PaintCanvas. 

2 

3Stateless functions over angles, points, grids and homographies. No widget or 

4document state — trivially unit-testable. PaintCanvas keeps thin delegating 

5wrappers so its call sites are unchanged. 

6""" 

7import math 

8 

9import numpy as np 

10from PySide6.QtCore import QPointF 

11from PySide6.QtGui import QTransform 

12 

13 

14def normalized_angle(angle): 

15 return ((float(angle) + 180.0) % 360.0) - 180.0 

16 

17 

18def rotated_vector(x, y, angle_degrees): 

19 angle = math.radians(float(angle_degrees)) 

20 return QPointF( 

21 float(x) * math.cos(angle) - float(y) * math.sin(angle), 

22 float(x) * math.sin(angle) + float(y) * math.cos(angle), 

23 ) 

24 

25 

26def quad_homography(source_points, target_points): 

27 matrix = [] 

28 values = [] 

29 for source, target in zip(source_points, target_points): 

30 x, y = float(source.x()), float(source.y()) 

31 u, v = float(target.x()), float(target.y()) 

32 matrix.append([x, y, 1, 0, 0, 0, -u * x, -u * y]) 

33 values.append(u) 

34 matrix.append([0, 0, 0, x, y, 1, -v * x, -v * y]) 

35 values.append(v) 

36 try: 

37 a, b, c, d, e, f, g, h = np.linalg.solve( 

38 np.asarray(matrix, dtype=np.float64), 

39 np.asarray(values, dtype=np.float64), 

40 ) 

41 except np.linalg.LinAlgError: 

42 return QTransform() 

43 return QTransform(a, d, g, b, e, h, c, f, 1.0) 

44 

45 

46def mesh_catmull_scalar(p0, p1, p2, p3, t): 

47 """制御点を通過するCatmull-Rom補間。""" 

48 t = max(0.0, min(1.0, float(t))) 

49 t2 = t * t 

50 t3 = t2 * t 

51 return 0.5 * ( 

52 2.0 * p1 

53 + (-p0 + p2) * t 

54 + (2.0*p0 - 5.0*p1 + 4.0*p2 - p3) * t2 

55 + (-p0 + 3.0*p1 - 3.0*p2 + p3) * t3 

56 ) 

57 

58 

59def regular_grid_points(rect, cols, rows=None): 

60 cols = max(2, int(cols)) 

61 rows = max(2, int(rows if rows is not None else cols)) 

62 width = max(0.0, rect.width()) 

63 height = max(0.0, rect.height()) 

64 return [ 

65 QPointF( 

66 rect.left() + width * gx / (cols - 1), 

67 rect.top() + height * gy / (rows - 1), 

68 ) 

69 for gy in range(rows) 

70 for gx in range(cols) 

71 ]