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

290 lines
No EOL
9.4 KiB
Python

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)