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")