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