From 4c99430d95671450987c7929363fd272f55f1296 Mon Sep 17 00:00:00 2001 From: Valerie Date: Sun, 25 May 2025 21:58:36 -0400 Subject: [PATCH] Update profile card generation in default.py by changing default text color to white, refining layout dimensions for better proportions, and enhancing visual elements such as the stats area and progress bar. Implement improved text shadowing and spacing for a more polished appearance. --- levelup/generator/styles/default.py | 208 ++++++++++++++-------------- 1 file changed, 107 insertions(+), 101 deletions(-) diff --git a/levelup/generator/styles/default.py b/levelup/generator/styles/default.py index c39f6ed..9e54412 100644 --- a/levelup/generator/styles/default.py +++ b/levelup/generator/styles/default.py @@ -190,77 +190,39 @@ def generate_default_profile( log.debug(f"PFP animated: {pfp_animated}, BG animated: {bg_animated}") # Setup - default_fill = (0, 0, 0) # Default fill color for text + default_fill = (255, 255, 255) # Default fill color for text stroke_width = 2 # Width of the stroke around text if square: desired_card_size = (450, 450) - # aspect_ratio = imgtools.calc_aspect_ratio(*desired_card_size) - name_y = 35 # Upper bound of username placement - stats_y = 160 # Upper bound of stats texts - blur_edge = 450 # Left bound of blur edge - bar_width = 550 # Length of level bar - bar_height = 40 # Height of level bar - bar_start = 475 # Left bound of level bar - bar_top = 380 # Top bound of level bar - stat_bottom = bar_top - 10 # Bottom bound of all stats - stat_start = bar_start + 10 # Left bound of all stats - stat_split = stat_start + 210 # Split between left and right stats - stat_end = 990 # Right bound of all stats - stat_offset = 45 # Offset between stats - circle_x = 60 # Left bound of profile circle - circle_y = 60 # Top bound of profile circle - star_text_x = 910 # Left bound of star text - star_text_y = 35 # Top bound of star text - star_icon_x = 850 # Left bound of star icon - star_icon_y = 35 # Top bound of star icon else: - # Ensure the card is the correct size and aspect ratio - desired_card_size = (1050, 450) - # aspect_ratio = imgtools.calc_aspect_ratio(*desired_card_size) - name_y = 35 # Upper bound of username placement - stats_y = 160 # Upper bound of stats texts - blur_edge = 450 # Left bound of blur edge - bar_width = 550 # Length of level bar - bar_height = 40 # Height of level bar - bar_start = 475 # Left bound of level bar - bar_top = 380 # Top bound of level bar - stat_bottom = bar_top - 10 # Bottom bound of all stats - stat_start = bar_start + 10 # Left bound of all stats - stat_split = stat_start + 210 # Split between left and right stats - stat_end = 990 # Right bound of all stats - stat_offset = 45 # Offset between stats - circle_x = 60 # Left bound of profile circle - circle_y = 60 # Top bound of profile circle - star_text_x = 910 # Left bound of star text - star_text_y = 35 # Top bound of star text - star_icon_x = 850 # Left bound of star icon - star_icon_y = 35 # Top bound of star icon - - # Create the stats layer with glass effect - stats_layer = Image.new("RGBA", desired_card_size, (0, 0, 0, 0)) - + # Reduce height for better proportions + desired_card_size = (1050, 350) + # Define the stats area with a modern glass effect stats_area = ( 400, # x1 - Start after profile picture 20, # y1 - Start near top 1000 if not square else 430, # x2 - End near right edge - 430 # y2 - End near bottom + 330 if not square else 430 # y2 - End near bottom ) - # Create a semi-transparent background for stats + # Create the stats layer with glass effect + stats_layer = Image.new("RGBA", desired_card_size, (0, 0, 0, 0)) + + # Create a semi-transparent background for stats with modern blur effect glass = Image.new("RGBA", desired_card_size, (0, 0, 0, 0)) glass_draw = ImageDraw.Draw(glass) - glass_draw.rounded_rectangle(stats_area, radius=20, fill=(0, 0, 0, 80)) + glass_draw.rounded_rectangle(stats_area, radius=25, fill=(0, 0, 0, 95)) - # Add a subtle gradient overlay + # Add a subtle gradient overlay for depth gradient = Image.new("RGBA", desired_card_size, (0, 0, 0, 0)) gradient_draw = ImageDraw.Draw(gradient) - for i in range(20): - opacity = int(80 * (1 - i/20)) + for i in range(30): + opacity = int(60 * (1 - i/30)) gradient_draw.rounded_rectangle( (stats_area[0], stats_area[1]+i, stats_area[2], stats_area[3]), - radius=20, + radius=25, fill=(255, 255, 255, opacity) ) @@ -268,14 +230,15 @@ def generate_default_profile( stats_layer = Image.alpha_composite(stats_layer, glass) stats_layer = Image.alpha_composite(stats_layer, gradient) - # Add a subtle border + # Add a subtle glow border border_draw = ImageDraw.Draw(stats_layer) - border_draw.rounded_rectangle( - stats_area, - radius=20, - outline=(255, 255, 255, 100), - width=2 - ) + for i in range(3): + border_draw.rounded_rectangle( + (stats_area[0]-i, stats_area[1]-i, stats_area[2]+i, stats_area[3]+i), + radius=25, + outline=(155, 17, 30, 100-i*30), + width=1 + ) # Draw stats with improved styling draw = ImageDraw.Draw(stats_layer) @@ -285,54 +248,78 @@ def generate_default_profile( if prestige > 0: level_text = f"P{prestige} • {level_text}" - level_font = ImageFont.truetype(str(font_path or imgtools.DEFAULT_FONT), 48) - level_y = stats_area[1] + 20 + # Use larger font for level display + level_font = ImageFont.truetype(str(font_path or imgtools.DEFAULT_FONT), 56) + level_y = stats_area[1] + 15 + + # Add subtle text shadow for depth + shadow_offset = 2 + draw.text( + (stats_area[0] + 20 + shadow_offset, level_y + shadow_offset), + level_text, + font=level_font, + fill=(0, 0, 0, 100) + ) draw.text( (stats_area[0] + 20, level_y), level_text, font=level_font, - fill=user_color, - stroke_width=2, - stroke_fill=(0, 0, 0) + fill=user_color ) - # Stats section - font_size = 32 - font = ImageFont.truetype(str(font_path or imgtools.DEFAULT_FONT), font_size) - spacing = 50 # Vertical spacing between stats + # Stats section with improved layout + title_font_size = 24 + value_font_size = 28 + title_font = ImageFont.truetype(str(font_path or imgtools.DEFAULT_FONT), title_font_size) + value_font = ImageFont.truetype(str(font_path or imgtools.DEFAULT_FONT), value_font_size) + spacing = 45 # Vertical spacing between stats # Starting positions start_x = stats_area[0] + 20 start_y = level_y + 80 - # Messages stat - draw.text((start_x, start_y), "💬", font=font, fill=stat_color) - draw.text((start_x + 40, start_y), f"{humanize_number(messages)}", font=font, fill=stat_color) - - # Voice time stat - draw.text((start_x, start_y + spacing), "🎤", font=font, fill=stat_color) - draw.text((start_x + 40, start_y + spacing), imgtools.abbreviate_time(voicetime), font=font, fill=stat_color) - - # Stars stat - draw.text((start_x, start_y + spacing * 2), "⭐", font=font, fill=stat_color) - draw.text((start_x + 40, start_y + spacing * 2), f"{humanize_number(stars)}", font=font, fill=stat_color) + # Helper function for stat rendering + def draw_stat(y_pos, icon, title, value, color=stat_color): + # Draw icon + draw.text((start_x, y_pos), icon, font=value_font, fill=color) + # Draw title + draw.text((start_x + 40, y_pos), f"{title}:", font=title_font, fill=(200, 200, 200)) + # Draw value with shadow for depth + value_x = start_x + 40 + value_y = y_pos + title_font_size + 2 + draw.text((value_x + 1, value_y + 1), value, font=value_font, fill=(0, 0, 0, 100)) + draw.text((value_x, value_y), value, font=value_font, fill=color) + return spacing + title_font_size + 5 - # Progress bar - progress = (current_xp - previous_xp) / (next_xp - previous_xp) - bar_width = stats_area[2] - stats_area[0] - 40 - bar_height = 25 - bar_y = stats_area[3] - 60 + # Draw each stat with title and value + current_y = start_y + current_y += draw_stat(current_y, "💬", "Messages", f"{humanize_number(messages)} sent") + current_y += draw_stat(current_y, "🎤", "Voice Time", imgtools.abbreviate_time(voicetime)) + current_y += draw_stat(current_y, "⭐", "Stars", humanize_number(stars)) - # Create progress bar background + # Right column stats + right_x = stats_area[0] + 300 + current_y = start_y + if balance is not None: + current_y += draw_stat(current_y, "💰", "Balance", f"{humanize_number(balance)} {currency_name}") + current_y += draw_stat(current_y, "🏆", "Rank", f"#{humanize_number(position)}") + + # Progress bar with modern design + bar_width = stats_area[2] - stats_area[0] - 40 + bar_height = 30 + bar_y = stats_area[3] - 60 + progress = (current_xp - previous_xp) / (next_xp - previous_xp) + + # Create progress bar background with gradient bar_bg = Image.new("RGBA", (bar_width, bar_height), (0, 0, 0, 100)) - progress_width = max(1, int(bar_width * progress)) # Ensure at least 1 pixel width + progress_width = max(1, int(bar_width * progress)) bar_progress = Image.new("RGBA", (progress_width, bar_height), level_bar_color + (200,)) - # Add gradient to progress bar + # Add shine effect to progress bar gradient = Image.new("RGBA", bar_progress.size, (0, 0, 0, 0)) gradient_draw = ImageDraw.Draw(gradient) for i in range(bar_height): - opacity = int(100 * (1 - i/bar_height)) + opacity = int(150 * (1 - abs(i - bar_height/2)/(bar_height/2))) gradient_draw.rectangle( (0, i, bar_progress.width, i+1), fill=(255, 255, 255, opacity) @@ -354,13 +341,32 @@ def generate_default_profile( stats_layer.paste(bar_bg, (start_x, bar_y), bar_bg) stats_layer.paste(bar_progress_masked, (start_x, bar_y), bar_progress_masked) - # XP text + # XP text with improved styling xp_font = ImageFont.truetype(str(font_path or imgtools.DEFAULT_FONT), 24) - xp_text = f"{humanize_number(current_xp)} / {humanize_number(next_xp)} XP" + xp_text = f"EXP: {humanize_number(current_xp)} / {humanize_number(next_xp)}" + xp_x = start_x + (bar_width - draw.textlength(xp_text, font=xp_font)) // 2 + + # Draw XP text with shadow draw.text( - (start_x, bar_y + bar_height + 5), + (xp_x + 1, bar_y - 30 + 1), xp_text, font=xp_font, + fill=(0, 0, 0, 100) + ) + draw.text( + (xp_x, bar_y - 30), + xp_text, + font=xp_font, + fill=(255, 255, 255) + ) + + # Progress percentage + percent_text = f"{int(progress * 100)}%" + percent_x = start_x + (bar_width - draw.textlength(percent_text, font=xp_font)) // 2 + draw.text( + (percent_x, bar_y + bar_height + 5), + percent_text, + font=xp_font, fill=stat_color, stroke_width=1, stroke_fill=(0, 0, 0) @@ -377,16 +383,16 @@ def generate_default_profile( card = imgtools.fit_aspect_ratio(card, desired_card_size) # Round the card corners - card = imgtools.round_image_corners(card, 20) + card = imgtools.round_image_corners(card, 25) # Create circular profile picture - pfp_size = (320, 320) # Profile picture size + pfp_size = (300, 300) # Slightly smaller profile picture pfp = pfp.resize(pfp_size, Image.Resampling.LANCZOS) pfp = imgtools.make_profile_circle(pfp) # Position profile picture on the left - pfp_x = 40 - pfp_y = (card.height - pfp_size[1]) // 2 + pfp_x = 50 + pfp_y = (desired_card_size[1] - pfp_size[1]) // 2 # Create a new image for final composition final_image = Image.new("RGBA", desired_card_size, (0, 0, 0, 0)) @@ -414,9 +420,9 @@ def generate_default_profile( card = card.convert("RGBA") card = imgtools.fit_aspect_ratio(card, desired_card_size) if blur: - blur_section = imgtools.blur_section(card, (blur_edge, 0, card.width, card.height)) + blur_section = imgtools.blur_section(card, (stats_area[0], 0, card.width, card.height)) # Paste onto the stats - card.paste(blur_section, (blur_edge, 0), blur_section) + card.paste(blur_section, (stats_area[0], 0), blur_section) card.paste(stats_layer, (0, 0), stats_layer) @@ -474,8 +480,8 @@ def generate_default_profile( # Paste items onto the card if blur: - blur_section = imgtools.blur_section(card_frame, (blur_edge, 0, card_frame.width, card_frame.height)) - card_frame.paste(blur_section, (blur_edge, 0), blur_section) + blur_section = imgtools.blur_section(card_frame, (stats_area[0], 0, card_frame.width, card_frame.height)) + card_frame.paste(blur_section, (stats_area[0], 0), blur_section) card_frame.paste(pfp, (circle_x, circle_y), pfp) card_frame.paste(stats_layer, (0, 0), stats_layer) @@ -547,9 +553,9 @@ def generate_default_profile( card_frame = card_frame.convert("RGBA") if blur: - blur_section = imgtools.blur_section(card_frame, (blur_edge, 0, card_frame.width, card_frame.height)) + blur_section = imgtools.blur_section(card_frame, (stats_area[0], 0, card_frame.width, card_frame.height)) # Paste onto the stats - card_frame.paste(blur_section, (blur_edge, 0), blur_section) + card_frame.paste(blur_section, (stats_area[0], 0), blur_section) if pfp_frame.mode != "RGBA": pfp_frame = pfp_frame.convert("RGBA")