utahman3431

Untitled

Mar 28th, 2025
21
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.53 KB | None | 0 0
  1. import numpy as np
  2. from PIL import Image, ImageDraw, ImageFont
  3. from skimage.color import rgb2lab, lab2rgb
  4. from sklearn.cluster import KMeans
  5. import math
  6. import sys
  7. import os
  8.  
  9. def create_polygon_path(center_x, center_y, radius, num_sides, rotation=0):
  10. points = []
  11. for i in range(num_sides):
  12. angle = 2 * math.pi * i / num_sides + rotation
  13. x = center_x + radius * math.cos(angle)
  14. y = center_y + radius * math.sin(angle)
  15. points.append((x, y))
  16. return points
  17. # """Creates a list of points representing the vertices of a polygon."""
  18. # points = [
  19. # (center_x + radius * math.cos(2 * math.pi * i / num_sides),
  20. # center_y + radius * math.sin(2 * math.pi * i / num_sides))
  21. # for i in range(num_sides)
  22. # ]
  23. # return points
  24.  
  25.  
  26. def get_polygon_color(image, x, y, radius, num_sides):
  27. """Gets the average color within a polygonal area, handling edge cases."""
  28. try:
  29. x1 = max(0, int(x - radius))
  30. y1 = max(0, int(y - radius))
  31. x2 = min(image.width, int(x + radius))
  32. y2 = min(image.height, int(y + radius))
  33.  
  34. if x1 >= x2 or y1 >= y2:
  35. return (0, 0, 0)
  36.  
  37. cropped_image = image.crop((x1, y1, x2, y2))
  38.  
  39. if cropped_image.mode != 'RGBA':
  40. cropped_image = cropped_image.convert('RGBA')
  41.  
  42. cropped_array = np.array(cropped_image)
  43.  
  44. mask = Image.new('L', (x2 - x1, y2 - y1), 0)
  45. draw = ImageDraw.Draw(mask)
  46. polygon_points = create_polygon_path(x - x1, y - y1, radius, num_sides, rotation=0)
  47. draw.polygon(polygon_points, fill=255)
  48.  
  49. masked_array = np.array(cropped_image)
  50. masked_array[np.array(mask) == 0] = [0, 0, 0, 0]
  51.  
  52. visible_pixels = masked_array[masked_array[:, :, 3] > 0][:, :3]
  53. return tuple(np.mean(visible_pixels, axis=0).astype(int)) if len(visible_pixels) > 0 else (0, 0, 0)
  54.  
  55. except Exception as e:
  56. print(f"Error calculating polygon color: {e}")
  57. return (0, 0, 0)
  58.  
  59. def create_square_grid(w, h, size):
  60. radius = size / 2
  61. positions = []
  62. y = -radius
  63. while y < h + radius:
  64. x = -radius
  65. while x < w + radius:
  66. if -radius < x < w and -radius < y < h:
  67. positions.append((x, y))
  68. x += size
  69. y += size
  70. return positions
  71.  
  72. def create_hexagon_grid(w, h, size):
  73. radius = size / 2
  74. vertical_spacing = int(size * math.sqrt(3) / 2)
  75. positions = []
  76. row = 0
  77. y = -vertical_spacing // 2
  78. while y < h + vertical_spacing:
  79. x_offset = size // 2 if row % 2 else 0
  80. x = -size // 2 + x_offset
  81. while x < w + size:
  82. if -size < x < w and -size < y < h:
  83. positions.append((x, y))
  84. x += size
  85. y += vertical_spacing
  86. row += 1
  87. return positions
  88.  
  89. def create_triangle_grid(w, h, size):
  90. radius = size / 2
  91. height = radius * math.sqrt(3)
  92. positions = []
  93. y = -radius
  94. while y < h + radius:
  95. x = -radius
  96. while x < w + radius:
  97. if -radius < x < w and -radius < y < h:
  98. positions.append((x, y))
  99. x += size
  100. y += height / 2
  101. y = height / 4 - radius
  102. while y < h + radius:
  103. x = size / 2 - radius
  104. while x < w + radius:
  105. if -radius < x < w and -radius < y < h:
  106. positions.append((x, y))
  107. x += size
  108. y += height / 2
  109. return positions
  110.  
  111.  
  112. def create_numbered_mosaic(image_path, output_path, shape="hexagon", size=30, n_colors=10, bg_gray=255):
  113. """Creates a numbered mosaic with the specified shape and a color key."""
  114. try:
  115. original_img = Image.open(image_path).convert('RGB')
  116. w, h = original_img.size
  117.  
  118. mosaic = Image.new('RGB', (w, h), (bg_gray, bg_gray, bg_gray))
  119. colored = Image.new('RGB', (w, h), (bg_gray, bg_gray, bg_gray))
  120. draw = ImageDraw.Draw(mosaic)
  121. colored_draw = ImageDraw.Draw(colored)
  122.  
  123. try:
  124. font = ImageFont.truetype("arial.ttf", size // 2)
  125. font2 = ImageFont.truetype("arial.ttf", 10)
  126. except IOError:
  127. try:
  128. font = ImageFont.truetype("LiberationSans-Bold.ttf", size // 2)
  129. font2 = ImageFont.truetype("DejaVuSerif.ttf", 10)
  130. except IOError:
  131. font = ImageFont.load_default()
  132. font.size = size // 2
  133. font2 = ImageFont.load_default()
  134. font2.size = 10
  135.  
  136. if shape == "triangle":
  137. num_sides = 3
  138. positions = create_triangle_grid(w, h, size)
  139. elif shape == "square":
  140. num_sides = 4
  141. positions = create_square_grid(w, h, size)
  142. else: # Default to hexagon
  143. shape = "hexagon"
  144. num_sides = 6
  145. positions = create_hexagon_grid(w, h, size)
  146.  
  147. radius = size / 2
  148. overlap_percentage = 2.0 # Adjust as needed for different shapes
  149.  
  150. colors = []
  151. for (x, y) in positions:
  152. avg_color = get_polygon_color(original_img, x + radius, y + radius, radius, num_sides)
  153. colors.append(avg_color)
  154.  
  155. if len(colors) > n_colors:
  156. lab_colors = rgb2lab(np.array(colors) / 255.).reshape(-1, 3)
  157. palette = lab2rgb(KMeans(n_colors).fit(lab_colors).cluster_centers_) * 255
  158. palette = [tuple(c.astype(int)) for c in palette]
  159. else:
  160. palette = list(set(colors))
  161.  
  162.  
  163. color_to_number = {color: str(i + 1) for i, color in enumerate(palette)}
  164.  
  165. for (x, y), color in zip(positions, colors):
  166. hex_color = min(palette, key=lambda c: sum((a - b)**2 for a, b in zip(color, c)))
  167.  
  168. rotation = math.pi / 4 if num_sides == 4 else 0 # Conditional rotation
  169. overlapped_radius = radius * (1 + overlap_percentage / 100)
  170. colored_points = create_polygon_path(x + radius, y + radius, overlapped_radius, num_sides,rotation)
  171. colored_draw.polygon(colored_points, fill=hex_color, outline=hex_color)
  172.  
  173. rotation = math.pi / 4 if num_sides == 4 else 0 # Conditional rotation
  174. points = create_polygon_path(x + radius, y + radius, radius, num_sides, rotation)
  175. draw.polygon(points, fill="white", outline=(0, 0, 0), width=1)
  176.  
  177. number = color_to_number[hex_color]
  178. text_bbox = draw.textbbox((0, 0), number, font=font)
  179. text_width = text_bbox[2] - text_bbox[0]
  180. text_height = text_bbox[3] - text_bbox[1]
  181. draw.text(
  182. (x + radius - text_width // 2, y + radius - text_height // 2 - size // 20),
  183. number, fill=(0, 0, 0), font=font
  184. )
  185.  
  186.  
  187. entry_height = 30
  188. box_height = 20
  189. header_height = 10
  190.  
  191. key_height = header_height + (entry_height * len(palette))
  192. key_img = Image.new('RGB', (80, key_height), (255, 255, 255))
  193. key_draw = ImageDraw.Draw(key_img)
  194.  
  195. for i, color in enumerate(palette):
  196. y_base = header_height + (i * entry_height)
  197.  
  198. key_draw.rectangle([5, y_base, 30, y_base + box_height], fill=color, outline=(0, 0, 0), width=1)
  199. key_draw.text((35, y_base + 5), f"{i + 1}", fill=(0, 0, 0), font=font2)
  200. key_draw.rectangle([50, y_base, 75, y_base + box_height], fill=(255, 255, 255), outline=(0, 0, 0), width=1)
  201.  
  202.  
  203. base_name, ext = os.path.splitext(output_path)
  204. key_img.save(f"{base_name}_key{ext}")
  205. mosaic.save(output_path)
  206. colored.save(f"{base_name}_colored{ext}")
  207. print(f"Success! Saved mosaic to {output_path} and color key to {base_name}_key{ext}")
  208.  
  209. except FileNotFoundError:
  210. print(f"Error: Input image not found at {image_path}")
  211. sys.exit(1)
  212. except Exception as e:
  213. print(f"An unexpected error occurred: {e}")
  214. sys.exit(1)
  215.  
  216.  
  217. if __name__ == "__main__":
  218. if len(sys.argv) < 4:
  219. print("Usage: python hex_mosaic.py input.jpg output.png shape [size=30] [colors=10] [bg_gray=255]")
  220. print("Valid shapes: hexagon, triangle, square")
  221. sys.exit(1)
  222.  
  223. try:
  224. create_numbered_mosaic(
  225. sys.argv[1],
  226. sys.argv[2],
  227. sys.argv[3],
  228. int(sys.argv[4]) if len(sys.argv) > 4 else 30,
  229. int(sys.argv[5]) if len(sys.argv) > 5 else 10,
  230. int(sys.argv[6]) if len(sys.argv) > 6 else 255
  231. )
  232. except ValueError:
  233. print("Error: size, colors, and bg_gray must be integers")
Add Comment
Please, Sign In to add comment