mirror of
https://github.com/Lilac-Rose/Lacie.git
synced 2026-09-12 02:56:30 -05:00
feat: gradient colours in profiles
bg_color now accepts up to 5 colours, separated by a space. These will appear as a horizontal gradient in the user's profile.
This commit is contained in:
@@ -1,15 +1,17 @@
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord import app_commands
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
from PIL import Image, ImageDraw, ImageFont, ImageColor
|
||||
from math import ceil
|
||||
import aiohttp
|
||||
import io
|
||||
import os
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
from textwrap import wrap
|
||||
from typing import Optional
|
||||
from .database import get_db, setup_db
|
||||
import numpy as np
|
||||
import traceback
|
||||
|
||||
FONTS_PATH = Path(__file__).parent / "fonts"
|
||||
|
||||
@@ -21,6 +23,47 @@ def list_fonts():
|
||||
fonts.append(os.path.splitext(f)[0])
|
||||
return fonts
|
||||
|
||||
def generate_gradient_image(colors: Array[String], width, height):
|
||||
try:
|
||||
# source: https://note.nkmk.me/en/python-numpy-generate-gradation-image/
|
||||
|
||||
def get_gradient_2d(start, stop, width, height, is_horizontal):
|
||||
if is_horizontal:
|
||||
return np.tile(np.linspace(start, stop, width), (height, 1))
|
||||
else:
|
||||
return np.tile(np.linspace(start, stop, height), (width, 1)).T
|
||||
|
||||
def get_gradient_3d(width, height, start_list, stop_list, is_horizontal_list):
|
||||
result = np.zeros((height, width, len(start_list)), dtype=float)
|
||||
|
||||
for i, (start, stop, is_horizontal) in enumerate(zip(start_list, stop_list, is_horizontal_list)):
|
||||
result[:, :, i] = get_gradient_2d(start, stop, width, height, is_horizontal)
|
||||
|
||||
return result
|
||||
|
||||
# Generate all the gradients
|
||||
gradient_images = []
|
||||
|
||||
gradient_image_width = ceil(width / (len(colors) - 1))
|
||||
|
||||
i = 0
|
||||
while i < len(colors) - 1:
|
||||
# colors is an array of stringified hex codes
|
||||
gradient_images.append(Image.fromarray(np.uint8(get_gradient_3d(gradient_image_width, height, ImageColor.getcolor(colors[i], "RGB"), ImageColor.getcolor(colors[i+1], "RGB"), (True, True, True)))))
|
||||
i += 1
|
||||
|
||||
# Paste them into one image
|
||||
i = 0
|
||||
final_img = Image.new('RGB', (width, height))
|
||||
for i, image in enumerate(gradient_images):
|
||||
final_img.paste(image, (gradient_image_width * i, 0))
|
||||
|
||||
return final_img
|
||||
except Exception as e:
|
||||
print(e)
|
||||
print(traceback.format_exc())
|
||||
|
||||
|
||||
class Profiles(commands.GroupCog, name="profile"):
|
||||
"""Profile commands"""
|
||||
|
||||
@@ -102,27 +145,31 @@ class Profiles(commands.GroupCog, name="profile"):
|
||||
pronouns="Your pronouns",
|
||||
about_me="A short description about you",
|
||||
fav_color="Your favorite color (name or hex)",
|
||||
bg_color="Background color (hex with #, e.g. #FF5733)",
|
||||
bg_color="Background color (hex with #, e.g. #FF5733), can have multiple colors as a gradient",
|
||||
fav_game="Your favorite game",
|
||||
fav_artist="Your favorite music artist",
|
||||
birthday="Your birthday (MM-DD)",
|
||||
font_name="Font to use (see /profile fonts)"
|
||||
)
|
||||
async def setprofile(self, interaction: discord.Interaction, pronouns: Optional[str] = None, about_me: Optional[str] = None, fav_color: Optional[str] = None, bg_color: Optional[str] = None, fav_game: Optional[str] = None, fav_artist: Optional[str] = None, birthday: Optional[str] = None, font_name: Optional[str] = None):
|
||||
async def setprofile(self, interaction: discord.Interaction, pronouns: str = None, about_me: str = None, fav_color: str = None, bg_color: str = None, fav_game: str = None, fav_artist: str = None, birthday: str = None, font_name: str = None):
|
||||
# Defer the response to show "thinking"
|
||||
await interaction.response.defer(thinking=True)
|
||||
|
||||
# Validate bg_color format
|
||||
if bg_color is not None:
|
||||
if not bg_color.startswith('#') or len(bg_color) not in [4, 7]:
|
||||
await interaction.followup.send("Background color must be a hex color starting with # (e.g. #FF5733 or #F57)")
|
||||
return
|
||||
# Validate hex characters
|
||||
try:
|
||||
int(bg_color[1:], 16)
|
||||
except ValueError:
|
||||
await interaction.followup.send("Invalid hex color format. Use # followed by hex digits (e.g. #FF5733)")
|
||||
return
|
||||
for color in bg_color.split(' '):
|
||||
if not color.startswith('#') or len(color) not in [4, 7]:
|
||||
await interaction.followup.send("Background colors must be hex colors starting with # (e.g. #FF5733 or #F57). To set gradients, do multiple hex colors separated with spaces (e.g. #FF5733 #BB153A).")
|
||||
return
|
||||
if len(bg_color) > 5:
|
||||
await interaction.followup.send("That's too many colors! Please use 5 colors or fewer.")
|
||||
return
|
||||
# Validate hex characters
|
||||
try:
|
||||
int(color[1:], 16)
|
||||
except ValueError:
|
||||
await interaction.followup.send("Invalid hex color format. Use # followed by hex digits (e.g. #FF5733)")
|
||||
return
|
||||
|
||||
if font_name and font_name not in list_fonts():
|
||||
await interaction.followup.send("That font is not avaliable. use /profile fonts to see the options.")
|
||||
@@ -187,15 +234,12 @@ class Profiles(commands.GroupCog, name="profile"):
|
||||
await interaction.followup.send("Your profile has been saved!")
|
||||
|
||||
@app_commands.command(name="view", description="View your or another user's profile.")
|
||||
async def profile(self, interaction: discord.Interaction, member: Optional[discord.Member] = None):
|
||||
async def profile(self, interaction: discord.Interaction, member: discord.Member = None):
|
||||
# Defer immediately to show "thinking"
|
||||
await interaction.response.defer(thinking=True)
|
||||
|
||||
member = member or interaction.user
|
||||
if not isinstance(member, discord.Member):
|
||||
await interaction.followup.send("This command can only be used in a server.", ephemeral=True)
|
||||
return
|
||||
|
||||
|
||||
db = get_db()
|
||||
cursor = db.cursor()
|
||||
cursor.execute("SELECT * FROM profiles WHERE user_id=?", (member.id,))
|
||||
@@ -243,15 +287,18 @@ class Profiles(commands.GroupCog, name="profile"):
|
||||
|
||||
# Parse background color - only accept hex with #
|
||||
base_color = "#0f1419" # default
|
||||
if bg_color and bg_color.startswith('#'):
|
||||
try:
|
||||
# Validate it's a proper hex color
|
||||
int(bg_color[1:], 16)
|
||||
base_color = bg_color
|
||||
except Exception:
|
||||
pass # Use default if invalid
|
||||
for color in bg_color.split(' '):
|
||||
if bg_color and bg_color.startswith('#'):
|
||||
try:
|
||||
# Validate it's a proper hex color
|
||||
int(bg_color[1:], 16)
|
||||
base_color = bg_color
|
||||
except Exception:
|
||||
pass # Use default if invalid
|
||||
|
||||
img = Image.new("RGB", (img_width, img_height), base_color)
|
||||
if len(bg_color.split(' ')) >= 2:
|
||||
img.paste(generate_gradient_image(bg_color.split(' '), img_width, img_height))
|
||||
draw = ImageDraw.Draw(img)
|
||||
|
||||
# Draw semi-transparent panel for avatar section
|
||||
@@ -475,4 +522,4 @@ class Profiles(commands.GroupCog, name="profile"):
|
||||
await interaction.followup.send(file=discord.File(buffer, "profile.png"))
|
||||
|
||||
async def setup(bot):
|
||||
await bot.add_cog(Profiles(bot))
|
||||
await bot.add_cog(Profiles(bot))
|
||||
|
||||
Reference in New Issue
Block a user