Skip to content

Commit 36a4020

Browse files
committed
feature: use cached embed colors
1 parent 611daf9 commit 36a4020

File tree

2 files changed

+67
-27
lines changed

2 files changed

+67
-27
lines changed

src/achievements.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ def create_achievement_embed(game, user, achievement, profile, current, total):
119119
completion = game.total_achievements_earned - total + current
120120
percentage = (completion / game.total_achievements) * 100
121121
unlock_percentage = (game.achievements[achievement.title]['NumAwardedHardcore'] / game.total_players_hardcore) * 100 if game.total_players_hardcore else 0
122-
most_common_color = get_discord_color(achievement.badge_url)
122+
most_common_color = get_discord_color(achievement.game_icon)
123123

124124
# Load emoji mappings
125125
with open('emoji.json') as f:

utils/image.py

Lines changed: 66 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,47 +2,87 @@
22
import requests
33
from io import BytesIO
44
import numpy as np
5+
import os
6+
import json
57
from sklearn.cluster import KMeans
68

7-
def get_discord_color(image_url, num_colors=5, crop_percentage=0.5):
9+
def cache_color(image_url, cache_file='image_cache.json', num_clusters=3):
810
"""
9-
Get the most distinct color from the center of an image for Discord usage.
11+
Cache the most vibrant and colorful area of the image to avoid recalculating it.
1012
1113
Args:
12-
image_url (str): The URL of the image to analyze.
13-
num_colors (int): Number of dominant colors to extract (default is 5).
14-
crop_percentage (float): Percentage of the image to keep in the center (default is 0.5).
14+
image_url (str): The URL of the image to cache.
15+
cache_file (str): Path to the JSON file used to store the cached color.
16+
num_clusters (int): Number of color clusters to detect.
1517
1618
Returns:
17-
int: The most distinct color in hexadecimal format.
18-
19-
Raises:
20-
(Exception): If there is an issue with fetching or processing the image.
19+
int: The most vibrant and colorful color in hexadecimal format.
2120
"""
21+
# Check if the cache file exists
22+
if os.path.exists(cache_file):
23+
with open(cache_file, 'r') as f:
24+
cached_data = json.load(f)
25+
if image_url in cached_data:
26+
# If the color is already cached, return it
27+
return cached_data[image_url]
28+
29+
# If color not cached, fetch and calculate it
2230
response = requests.get(image_url)
2331
img = Image.open(BytesIO(response.content))
32+
img = img.convert("RGB") # Ensure the image is in RGB format
2433

25-
# Calculate the crop dimensions
26-
width, height = img.size
27-
crop_width = int(width * crop_percentage)
28-
crop_height = int(height * crop_percentage)
29-
left = (width - crop_width) // 2
30-
top = (height - crop_height) // 2
31-
right = left + crop_width
32-
bottom = top + crop_height
34+
# Resize the image for faster processing
35+
img = img.resize((img.width // 2, img.height // 2))
3336

34-
img = img.crop((left, top, right, bottom))
37+
# Convert the image to a NumPy array
38+
img_data = np.array(img)
3539

36-
img = img.resize((img.width // 2, img.height // 2)) # Resize for faster processing
40+
# Reshape the image data into a 2D array of pixels for clustering
41+
pixels = img_data.reshape((-1, 3))
3742

38-
img = img.convert("RGB") # Ensure the image is in RGB format
43+
# Remove colors that are too close to black/gray/white
44+
mask = np.linalg.norm(pixels, axis=1) > 50 # A simple threshold to remove very dark colors
45+
filtered_pixels = pixels[mask]
46+
47+
# Perform KMeans clustering to find color clusters
48+
kmeans = KMeans(n_clusters=num_clusters, random_state=0)
49+
kmeans.fit(filtered_pixels)
50+
51+
# Get the centers of the clusters (the most common colors)
52+
cluster_centers = kmeans.cluster_centers_
53+
54+
# Calculate the distance from each cluster center to the black/white axis (i.e., evaluate vibrancy)
55+
distances = np.linalg.norm(cluster_centers - np.array([0, 0, 0]), axis=1)
56+
57+
# Get the most vibrant cluster (the one that is farthest from black)
58+
most_vibrant_cluster_idx = np.argmax(distances)
59+
vibrant_color = cluster_centers[most_vibrant_cluster_idx]
3960

40-
img_array = np.array(img)
41-
img_flattened = img_array.reshape(-1, 3)
61+
# Convert the RGB color to hexadecimal format
62+
vibrant_color_hex = int('0x{:02x}{:02x}{:02x}'.format(*vibrant_color.astype(int)), 16)
4263

43-
kmeans = KMeans(n_clusters=num_colors)
44-
kmeans.fit(img_flattened)
64+
# Cache the color
65+
if not os.path.exists(cache_file):
66+
cached_data = {}
4567

46-
dominant_color = kmeans.cluster_centers_[np.argmax(np.bincount(kmeans.labels_))]
68+
cached_data[image_url] = vibrant_color_hex
4769

48-
return int('0x{:02x}{:02x}{:02x}'.format(*dominant_color.astype(int)), 16)
70+
# Save the color to the cache file
71+
with open(cache_file, 'w') as f:
72+
json.dump(cached_data, f)
73+
74+
return vibrant_color_hex
75+
76+
def get_discord_color(image_url, cache_file='image_cache.json', num_clusters=3):
77+
"""
78+
Get the most vibrant and colorful color (Discord color) from the image for caching.
79+
80+
Args:
81+
image_url (str): The URL of the image to analyze.
82+
cache_file (str): Path to the JSON file used to store cached color.
83+
num_clusters (int): Number of color clusters to detect.
84+
85+
Returns:
86+
int: The most vibrant and colorful color in hexadecimal format.
87+
"""
88+
return cache_color(image_url, cache_file, num_clusters)

0 commit comments

Comments
 (0)