170 lines
No EOL
6.6 KiB
Python
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) |