Update pyproject.toml to include Python and Wand dependencies. Modify default.py to enhance profile layout with adjusted positions and improved glass morphism effect. Update profile.py to set modern profile generation as the default style, ensuring a more contemporary user experience.
Some checks are pending
Run pre-commit / Run pre-commit (push) Waiting to run

This commit is contained in:
Valerie 2025-05-26 20:58:12 -04:00
parent 9c5924418d
commit 653b16cb5e
4 changed files with 187 additions and 10 deletions

View file

@ -208,19 +208,19 @@ def generate_default_profile(
# Define the stats area with a modern glass effect
stats_area = (
380, # x1 - Start after profile picture
15, # x1 - Start from left edge with small padding
15, # y1 - Start near top
1020, # x2 - End near right edge
1035, # x2 - End near right edge
305 # y2 - Adjusted height
)
# Create the stats layer with glass effect
stats_layer = Image.new("RGBA", desired_card_size, (0, 0, 0, 0))
# Create a modern glass morphism effect for stats
# Create a modern glass morphism effect for entire background
glass = Image.new("RGBA", desired_card_size, (0, 0, 0, 0))
glass_draw = ImageDraw.Draw(glass)
glass_draw.rounded_rectangle(stats_area, radius=25, fill=(255, 255, 255, 25)) # Lighter, more modern glass
glass_draw.rounded_rectangle(stats_area, radius=25, fill=(0, 0, 0, 100)) # Darker, semi-transparent background
# Add a subtle gradient overlay for depth
gradient = Image.new("RGBA", desired_card_size, (0, 0, 0, 0))
@ -262,13 +262,13 @@ def generate_default_profile(
# Add subtle text shadow for depth
shadow_offset = 2
draw.text(
(stats_area[0] + 20 + shadow_offset, level_y + shadow_offset),
(stats_area[0] + 340 + shadow_offset, level_y + shadow_offset), # Adjusted x position
level_text,
font=level_font,
fill=(0, 0, 0, 80) # Reduced shadow opacity
)
draw.text(
(stats_area[0] + 20, level_y),
(stats_area[0] + 340, level_y), # Adjusted x position
level_text,
font=level_font,
fill=user_color
@ -282,7 +282,7 @@ def generate_default_profile(
spacing = 45 # Increased spacing
# Starting positions
start_x = stats_area[0] + 20
start_x = stats_area[0] + 340 # Adjusted x position
start_y = level_y + 75
# Helper function for stat rendering with improved spacing

View file

@ -0,0 +1,170 @@
import typing as t
from pathlib import Path
from io import BytesIO
import logging
from wand.image import Image
from wand.drawing import Drawing
from wand.color import Color
from redbot.core.utils.chat_formatting import humanize_number
try:
from .. import imgtools
except ImportError:
import imgtools
log = logging.getLogger("red.vrt.levelup.generator.styles.modern")
def generate_modern_profile(
background_bytes: t.Optional[t.Union[bytes, str]] = None,
avatar_bytes: t.Optional[t.Union[bytes, str]] = None,
username: str = "Spartan117",
status: str = "online",
level: int = 3,
messages: int = 420,
voicetime: int = 3600,
stars: int = 69,
prestige: int = 0,
prestige_emoji: t.Optional[t.Union[bytes, str]] = None,
balance: int = 0,
currency_name: str = "Credits",
previous_xp: int = 100,
current_xp: int = 125,
next_xp: int = 200,
position: int = 3,
role_icon: t.Optional[t.Union[bytes, str]] = None,
blur: bool = False,
base_color: t.Tuple[int, int, int] = (255, 255, 255),
user_color: t.Optional[t.Tuple[int, int, int]] = None,
stat_color: t.Optional[t.Tuple[int, int, int]] = None,
level_bar_color: t.Optional[t.Tuple[int, int, int]] = None,
font_path: t.Optional[t.Union[str, Path]] = None,
render_gif: bool = False,
debug: bool = False,
**kwargs,
) -> t.Tuple[bytes, bool]:
"""Generate a modern, sleek profile card using Wand/ImageMagick"""
# Set default colors for modern theme
user_color = user_color or (88, 101, 242) # Discord Blurple
stat_color = stat_color or (255, 255, 255) # White
level_bar_color = level_bar_color or (88, 101, 242) # Discord Blurple
# Create base canvas (1050x320)
with Image(width=1050, height=320) as card:
# Set up background
if background_bytes:
with Image(blob=background_bytes) as bg:
bg.transform(resize='1050x320^')
bg.crop(width=1050, height=320, gravity='center')
if blur:
bg.gaussian_blur(sigma=15)
card.composite(bg, 0, 0)
else:
# Create gradient background
card.gradient('linear-gradient', 'navy-darkblue')
# Add frosted glass effect overlay
with Image(width=1050, height=320, background=Color('rgba(255, 255, 255, 0.1)')) as overlay:
overlay.gaussian_blur(sigma=50)
card.composite(overlay, 0, 0)
# Create circular avatar mask
with Drawing() as draw:
# Avatar circle (left side)
avatar_size = 200
avatar_x = 60
avatar_y = 60
if avatar_bytes:
with Image(blob=avatar_bytes) as avatar:
# Resize avatar
avatar.resize(avatar_size, avatar_size)
# Create circular mask
with Image(width=avatar_size, height=avatar_size, background=Color('transparent')) as mask:
draw.circle((avatar_size/2, avatar_size/2), (0, avatar_size/2))
draw.draw(mask)
# Apply mask to avatar
avatar.composite_channel('alpha', mask, 'copy_alpha', 0, 0)
# Add avatar to card
card.composite(avatar, avatar_x, avatar_y)
# Add modern stats panel
with Image(width=700, height=280, background=Color('rgba(0, 0, 0, 0.5)')) as stats_panel:
stats_panel.gaussian_blur(sigma=2)
stats_panel.border_color = Color('rgba(255, 255, 255, 0.1)')
stats_panel.border_width = 1
card.composite(stats_panel, 310, 20)
# Add text elements
draw.font = str(font_path) if font_path else '/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf'
draw.font_size = 40
draw.fill_color = Color('rgb({}, {}, {})'.format(*user_color))
# Level text
level_text = f"LEVEL {level}"
if prestige > 0:
level_text = f"P{prestige}{level_text}"
draw.text(340, 70, level_text)
# Stats
draw.font_size = 25
draw.fill_color = Color('white')
y_pos = 120
# Left column
draw.text(340, y_pos, f"Messages: {humanize_number(messages)}")
draw.text(340, y_pos + 40, f"Voice Time: {imgtools.abbreviate_time(voicetime)}")
draw.text(340, y_pos + 80, f"Stars: {humanize_number(stars)}")
# Right column
if balance is not None:
draw.text(600, y_pos, f"Balance: {humanize_number(balance)} {currency_name}")
draw.text(600, y_pos + 40, f"Rank: #{humanize_number(position)}")
# Progress bar
progress = (current_xp - previous_xp) / (next_xp - previous_xp)
bar_width = 620
bar_height = 30
bar_x = 340
bar_y = 240
# Background bar
draw.fill_color = Color('rgba(0, 0, 0, 0.3)')
draw.rectangle(bar_x, bar_y, bar_x + bar_width, bar_y + bar_height)
# Progress bar
progress_width = int(bar_width * progress)
draw.fill_color = Color('rgb({}, {}, {})'.format(*level_bar_color))
if progress_width > 0:
draw.rectangle(bar_x, bar_y, bar_x + progress_width, bar_y + bar_height)
# XP Text
draw.font_size = 20
draw.fill_color = Color('white')
xp_text = f"XP: {humanize_number(current_xp)} / {humanize_number(next_xp)}"
draw.text(bar_x + (bar_width - len(xp_text) * 10) / 2, bar_y - 15, xp_text)
# Progress percentage
percent_text = f"{int(progress * 100)}%"
draw.text(bar_x + progress_width - 30, bar_y + 5, percent_text)
# Apply all drawings
draw.draw(card)
# Save the result
card.format = 'webp'
card.compression_quality = 95
buffer = BytesIO()
card.save(buffer)
return buffer.getvalue(), False
if __name__ == "__main__":
# Test code
logging.basicConfig(level=logging.DEBUG)
test_avatar = (imgtools.ASSETS / "tests" / "default.png").read_bytes()
res, _ = generate_modern_profile(
avatar_bytes=test_avatar,
debug=True
)
(imgtools.ASSETS / "tests" / "modern_result.webp").write_bytes(res)

View file

@ -15,7 +15,7 @@ from redbot.core.utils.chat_formatting import box, humanize_number
from ..abc import MixinMeta
from ..common import formatter, utils
from ..common.models import Profile
from ..generator.styles import default, runescape
from ..generator.styles import default, runescape, modern
log = logging.getLogger("red.vrt.levelup.shared.profile")
_ = Translator("LevelUp", __file__)
@ -325,12 +325,15 @@ class ProfileFormatting(MixinMeta):
except Exception as e:
log.error("Failed to fetch profile from internal API", exc_info=e)
# By default we'll use the bundled generator
# By default we'll use the modern generator
funcs = {
"default": default.generate_default_profile,
"modern": modern.generate_modern_profile, # New default modern style
"default": default.generate_default_profile, # Old style
"runescape": runescape.generate_runescape_profile,
}
profile_style = profile.style if profile.style in funcs else "modern" # Default to modern style
def _run() -> discord.File:
img_bytes, animated = funcs[profile_style](**kwargs)
ext = "gif" if animated else "webp"

View file

@ -7,3 +7,7 @@
line-length = 99
target-version = ['py38']
include = '\.pyi?$'
[tool.poetry.dependencies]
python = ">=3.8.1,<4.0"
Wand = "^0.6.11" # ImageMagick Python bindings for modern image processing