maybe a fork of sparrowhe's "bluesky circle" webapp, to frontend only?
1import requests
2from PIL import Image, ImageOps, ImageDraw
3from io import BytesIO
4import numpy as np
5import math
6import os
7
8
9def check_cache_img(url):
10 # Check if the image is already cached
11 cache_path = os.path.join('static', 'avatars', os.path.basename(url.split('/')[-1]+'.jpg'))
12 if os.path.exists(cache_path):
13 return cache_path
14 return None
15
16def load_image_as_circle(image_url, radius, proxies={
17 'https': 'http://127.0.0.1:7897',
18 'http': 'http://127.0.0.1:7897'
19}):
20 try:
21 # Add proxy support in the request
22 if check_cache_img(image_url):
23 print('Using cached image')
24 img = Image.open(check_cache_img(image_url))
25 else:
26 response = requests.get(image_url, proxies=proxies)
27 img = Image.open(BytesIO(response.content))
28 img.save(os.path.join('static', 'avatars', os.path.basename(image_url.split('/')[-1])+'.jpg'))
29 response = requests.get(image_url, proxies=proxies)
30 img = Image.open(BytesIO(response.content))
31
32 # Calculate diameter from radius
33 diameter = radius * 2
34
35 # Ensure source image is larger or equal to target size before resizing
36 if img.size[0] < diameter or img.size[1] < diameter:
37 img = img.resize((diameter, diameter), Image.Resampling.LANCZOS)
38 else:
39 # Resize using LANCZOS resampling
40 img = img.resize((diameter, diameter), Image.Resampling.LANCZOS)
41
42 # Create a mask to crop the image into a circle
43 mask = Image.new('L', (diameter, diameter), 0)
44 draw = ImageDraw.Draw(mask)
45 draw.ellipse((0, 0, diameter, diameter), fill=255)
46
47 # Apply the mask to create a circular image
48 img = ImageOps.fit(img, (diameter, diameter), centering=(0.5, 0.5))
49 img.putalpha(mask)
50
51 return img
52 except Exception as e:
53 print(f"Error loading image: {e}")
54 return None
55
56# Function to plot avatars in circular layout with varying sizes using PIL
57def plot_avatars_full_circle(friends_dict, center_avatar_url, proxies=None):
58 # Create a blank 1024x1024 white canvas
59 canvas_size = (800, 800)
60 canvas = Image.new("RGBA", canvas_size, (255, 255, 255, 255))
61
62 # Define the sizes for the avatars in each circle
63 base_radius = int(35*1.25) # Radius for avatars in the innermost circle
64 center_avatar_size = 150 # Size for the center avatar (radius 70 means diameter 140)
65 size_step = 5 # Decrease size by 10 pixels for each layer
66 min_avatar_size = 15 # Minimum size to prevent overly small avatars
67
68 # Load and place center avatar
69 center_img = load_image_as_circle(center_avatar_url, center_avatar_size // 2, proxies=proxies)
70 if center_img:
71 center_position = (canvas_size[0] // 2 - center_avatar_size // 2, canvas_size[1] // 2 - center_avatar_size // 2)
72 canvas.paste(center_img, center_position, center_img)
73
74 # Sort friends based on their reply_score, highest score first
75 sorted_friends = sorted(friends_dict.items(), key=lambda x: x[1]['reply_score'], reverse=True)
76
77 # Plot avatars based on score
78 radius_step = 85 # Distance between each circle in pixels
79 initial_radius = 140 # Radius for the first circle
80 friend_idx = 0
81 radius = initial_radius
82 layer = 0
83
84 while friend_idx < len(sorted_friends) and layer < 4:
85 # Calculate avatar size for this layer (decrease by size_step for each layer)
86 avatar_radius = max(base_radius - layer * size_step, min_avatar_size // 2) # Use radius now
87 avatar_size = avatar_radius * 2 # Diameter for resizing
88
89 # Calculate how many avatars fit in the current circle
90 num_in_current_circle = int(2 * np.pi * radius / (avatar_size)) # Adjusted based on avatar size
91 theta_step = 2 * np.pi / num_in_current_circle # Angle between avatars
92 rotation_offset = layer * (np.pi / 12) # Rotate 15 degrees for each layer
93
94 for i in range(num_in_current_circle):
95 if friend_idx >= len(sorted_friends):
96 break # Stop if no more friends to place
97
98 friend_data = sorted_friends[friend_idx][1]
99 avatar_url = friend_data['avatar']
100 theta = i * theta_step + rotation_offset # Apply rotation
101
102 # Calculate avatar position
103 x = int(canvas_size[0] // 2 + radius * math.cos(theta) - avatar_radius) # Center the circle
104 y = int(canvas_size[1] // 2 + radius * math.sin(theta) - avatar_radius)
105
106 # Load and place circular avatar
107 img = load_image_as_circle(avatar_url, avatar_radius, proxies=proxies)
108 if img:
109 canvas.paste(img, (x, y), img)
110
111 friend_idx += 1
112
113 # Move to the next circle
114 radius += radius_step
115 layer += 1
116
117 buf = BytesIO()
118 canvas.save(buf, format='PNG')
119 data = buf.getvalue()
120 buf.close()
121 return data