Ruby-Cogs/levelup/generator/styles/modern.py

170 lines
No EOL
6.6 KiB
Python

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)