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)