You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

133 lines
3.0 KiB

2 months ago
  1. from __future__ import annotations
  2. import math
  3. import numpy as np
  4. def downsample(img, factor):
  5. """
  6. Downsample an image along both dimensions by some factor
  7. """
  8. assert img.shape[0] % factor == 0
  9. assert img.shape[1] % factor == 0
  10. img = img.reshape(
  11. [img.shape[0] // factor, factor, img.shape[1] // factor, factor, 3]
  12. )
  13. img = img.mean(axis=3)
  14. img = img.mean(axis=1)
  15. return img
  16. def fill_coords(img, fn, color):
  17. """
  18. Fill pixels of an image with coordinates matching a filter function
  19. """
  20. for y in range(img.shape[0]):
  21. for x in range(img.shape[1]):
  22. yf = (y + 0.5) / img.shape[0]
  23. xf = (x + 0.5) / img.shape[1]
  24. if fn(xf, yf):
  25. img[y, x] = color
  26. return img
  27. def rotate_fn(fin, cx, cy, theta):
  28. def fout(x, y):
  29. x = x - cx
  30. y = y - cy
  31. x2 = cx + x * math.cos(-theta) - y * math.sin(-theta)
  32. y2 = cy + y * math.cos(-theta) + x * math.sin(-theta)
  33. return fin(x2, y2)
  34. return fout
  35. def point_in_line(x0, y0, x1, y1, r):
  36. p0 = np.array([x0, y0], dtype=np.float32)
  37. p1 = np.array([x1, y1], dtype=np.float32)
  38. dir = p1 - p0
  39. dist = np.linalg.norm(dir)
  40. dir = dir / dist
  41. xmin = min(x0, x1) - r
  42. xmax = max(x0, x1) + r
  43. ymin = min(y0, y1) - r
  44. ymax = max(y0, y1) + r
  45. def fn(x, y):
  46. # Fast, early escape test
  47. if x < xmin or x > xmax or y < ymin or y > ymax:
  48. return False
  49. q = np.array([x, y])
  50. pq = q - p0
  51. # Closest point on line
  52. a = np.dot(pq, dir)
  53. a = np.clip(a, 0, dist)
  54. p = p0 + a * dir
  55. dist_to_line = np.linalg.norm(q - p)
  56. return dist_to_line <= r
  57. return fn
  58. def point_in_circle(cx, cy, r):
  59. def fn(x, y):
  60. return (x - cx) * (x - cx) + (y - cy) * (y - cy) <= r * r
  61. return fn
  62. def point_in_rect(xmin, xmax, ymin, ymax):
  63. def fn(x, y):
  64. return x >= xmin and x <= xmax and y >= ymin and y <= ymax
  65. return fn
  66. def point_in_triangle(a, b, c):
  67. a = np.array(a, dtype=np.float32)
  68. b = np.array(b, dtype=np.float32)
  69. c = np.array(c, dtype=np.float32)
  70. def fn(x, y):
  71. v0 = c - a
  72. v1 = b - a
  73. v2 = np.array((x, y)) - a
  74. # Compute dot products
  75. dot00 = np.dot(v0, v0)
  76. dot01 = np.dot(v0, v1)
  77. dot02 = np.dot(v0, v2)
  78. dot11 = np.dot(v1, v1)
  79. dot12 = np.dot(v1, v2)
  80. # Compute barycentric coordinates
  81. inv_denom = 1 / (dot00 * dot11 - dot01 * dot01)
  82. u = (dot11 * dot02 - dot01 * dot12) * inv_denom
  83. v = (dot00 * dot12 - dot01 * dot02) * inv_denom
  84. # Check if point is in triangle
  85. return (u >= 0) and (v >= 0) and (u + v) < 1
  86. return fn
  87. def highlight_img(img, color=(255, 255, 255), alpha=0.30):
  88. """
  89. Add highlighting to an image
  90. """
  91. blend_img = img + alpha * (np.array(color, dtype=np.uint8) - img)
  92. blend_img = blend_img.clip(0, 255).astype(np.uint8)
  93. img[:, :, :] = blend_img