import typing as t from pathlib import Path from io import BytesIO, StringIO import logging import svgwrite 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 create_vector_profile( width: int = 1050, height: int = 320, avatar_size: int = 200, username: str = "", level: int = 1, messages: int = 0, voicetime: int = 0, stars: int = 0, prestige: int = 0, balance: t.Optional[int] = None, currency_name: str = "", progress: float = 0, current_xp: int = 0, next_xp: int = 0, position: int = 0, user_color: t.Tuple[int, int, int] = (88, 101, 242), stat_color: t.Tuple[int, int, int] = (255, 255, 255), level_bar_color: t.Tuple[int, int, int] = (88, 101, 242), ) -> str: """Generate SVG profile card""" # Create SVG document dwg = svgwrite.Drawing(size=(width, height)) # Define gradients # Main background gradient grad = dwg.defs.add(dwgwrite.gradients.LinearGradient(id="background")) grad.add_stop_color(0, "rgb(20, 20, 30)") grad.add_stop_color(1, "rgb(40, 40, 60)") # Progress bar gradient prog_grad = dwg.defs.add(dwgwrite.gradients.LinearGradient(id="progress", x1="0%", y1="0%", x2="0%", y2="100%")) r, g, b = level_bar_color prog_grad.add_stop_color(0, f"rgb({r}, {g}, {b})") prog_grad.add_stop_color(0.5, f"rgb({min(r+20, 255)}, {min(g+20, 255)}, {min(b+20, 255)})") prog_grad.add_stop_color(1, f"rgb({r}, {g}, {b})") # Add background dwg.add(dwg.rect(insert=(0, 0), size=(width, height), fill="url(#background)")) # Add glass panel with blur effect (using SVG filters) blur = dwg.defs.add(dwg.filter(id="blur")) blur.feGaussianBlur(in_="SourceGraphic", stdDeviation=8) glass = dwg.add(dwg.rect( insert=(15, 15), size=(width-30, height-30), rx=25, ry=25, fill="rgba(255, 255, 255, 0.1)", filter="url(#blur)", stroke="rgba(255, 255, 255, 0.2)", stroke_width=1 )) # Add stats container stats = dwg.add(dwg.g(id="stats", transform=f"translate({avatar_size + 80}, 40)")) # Add level text with glow effect glow = dwg.defs.add(dwg.filter(id="glow")) glow.feGaussianBlur(in_="SourceAlpha", stdDeviation=2) glow.feOffset(dx=0, dy=0) glow.feComponentTransfer().feFuncA(type="linear", slope=0.5) glow.feBlend(in2="SourceGraphic", mode="normal") level_text = f"LEVEL {level}" if prestige > 0: level_text = f"P{prestige} • {level_text}" r, g, b = user_color stats.add(dwg.text( level_text, insert=(0, 0), fill=f"rgb({r}, {g}, {b})", filter="url(#glow)", style="font-size:56px; font-weight:bold; font-family:Arial" )) # Add stats with vector icons def add_stat(x: int, y: int, icon: str, label: str, value: str): group = dwg.g(transform=f"translate({x}, {y})") # Add icon group.add(dwg.text( icon, insert=(0, 0), fill="white", style="font-size:28px; font-family:Arial" )) # Add label group.add(dwg.text( f"{label}:", insert=(40, 0), fill="rgb(220, 220, 220)", style="font-size:28px; font-family:Arial" )) # Add value with glow group.add(dwg.text( value, insert=(140, 0), fill="white", filter="url(#glow)", style="font-size:32px; font-weight:bold; font-family:Arial" )) return group # Left column stats stats.add(add_stat(0, 80, "💬", "Messages", humanize_number(messages))) stats.add(add_stat(0, 130, "🎤", "Voice", imgtools.abbreviate_time(voicetime))) stats.add(add_stat(0, 180, "⭐", "Stars", humanize_number(stars))) # Right column stats if balance is not None: stats.add(add_stat(320, 80, "💰", "Balance", f"{humanize_number(balance)} {currency_name}")) stats.add(add_stat(320, 130, "🏆", "Rank", f"#{humanize_number(position)}")) # Progress bar bar_width = 620 bar_height = 30 bar_x = 0 bar_y = 220 # Progress bar background stats.add(dwg.rect( insert=(bar_x, bar_y), size=(bar_width, bar_height), rx=bar_height//2, ry=bar_height//2, fill="rgba(0, 0, 0, 0.3)" )) # Progress bar fill progress_width = int(bar_width * progress) if progress_width > 0: stats.add(dwg.rect( insert=(bar_x, bar_y), size=(progress_width, bar_height), rx=bar_height//2, ry=bar_height//2, fill="url(#progress)" )) # XP Text xp_text = f"XP: {humanize_number(current_xp)} / {humanize_number(next_xp)}" stats.add(dwg.text( xp_text, insert=(bar_x + (bar_width - len(xp_text) * 10) / 2, bar_y - 15), fill="white", style="font-size:20px; font-family:Arial" )) # Progress percentage percent_text = f"{int(progress * 100)}%" stats.add(dwg.text( percent_text, insert=(bar_x + progress_width - 30, bar_y + 20), fill="white", style="font-size:24px; font-weight:bold; font-family:Arial" )) # Return SVG as string return dwg.tostring() 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 vector graphics""" # 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 # Calculate progress progress = (current_xp - previous_xp) / (next_xp - previous_xp) # Generate SVG svg_data = create_vector_profile( username=username, level=level, messages=messages, voicetime=voicetime, stars=stars, prestige=prestige, balance=balance, currency_name=currency_name, progress=progress, current_xp=current_xp, next_xp=next_xp, position=position, user_color=user_color, stat_color=stat_color, level_bar_color=level_bar_color, ) # Convert SVG to image with Image(width=1050, height=320) as card: # Set up background if provided 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: # Use gradient background card.gradient('linear-gradient', 'navy-darkblue') # Add SVG elements with Image(blob=svg_data.encode(), format='svg') as svg: card.composite(svg, 0, 0) # Add avatar if provided if avatar_bytes: with Image(blob=avatar_bytes) as avatar: # Create circular avatar avatar.resize(200, 200) avatar.virtual_pixel = 'transparent' avatar.format = 'png' avatar.alpha_channel = True # Create circular mask with Image(width=200, height=200, background=Color('transparent')) as mask: mask.format = 'png' with Drawing() as draw: draw.fill_color = Color('white') draw.circle((100, 100), (0, 100)) draw.draw(mask) avatar.composite_channel('alpha', mask, 'copy_alpha', 0, 0) # Add avatar to card card.composite(avatar, 60, 60) # Save as WebP 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)