from flask import Flask, send_file, request from PIL import Image, ImageDraw, ImageFont import requests import io import os from urllib.request import urlopen app = Flask(__name__) def create_gradient_vertical(width, height, color_top, color_bottom): """Create a smooth vertical gradient""" base = Image.new('RGB', (width, height), color_top) for y in range(height): r = int(color_top[0] + (color_bottom[0] - color_top[0]) * y / height) g = int(color_top[1] + (color_bottom[1] - color_top[1]) * y / height) b = int(color_top[2] + (color_bottom[2] - color_top[2]) * y / height) for x in range(width): base.putpixel((x, y), (r, g, b)) return base def get_font(size): """Try to load Comic Sans, fallback to others""" font_paths = [ "comic.ttf", "C:/Windows/Fonts/comic.ttf", "/System/Library/Fonts/Supplemental/Comic Sans MS.ttf", "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf", "arial.ttf", "C:/Windows/Fonts/arial.ttf" ] for path in font_paths: try: return ImageFont.truetype(path, size) except: continue return ImageFont.load_default() @app.route('/design') def generate_card(): player_id = request.args.get('id', '89133456301399') # Fetch player data try: response = requests.get(f'https://efootdb.vercel.app/api/playerinfo?id={player_id}') data = response.json() if not data.get('success'): return "Player not found", 404 info = data['info'] except Exception as e: return f"Error fetching data: {str(e)}", 500 # Card dimensions (16:9 ratio) W, H = 1920, 1080 # Dark blue theme colors DARK_NAVY = (10, 15, 35) DEEP_BLUE = (15, 25, 55) MEDIUM_BLUE = (25, 45, 85) ACCENT_BLUE = (60, 100, 180) LIGHT_BLUE = (100, 150, 220) ELECTRIC_BLUE = (80, 180, 255) WHITE = (255, 255, 255) LIGHT_GRAY = (200, 210, 230) GOLD = (255, 215, 0) GREEN = (80, 220, 120) RED = (255, 80, 80) ORANGE = (255, 160, 60) # Create base with gradient img = create_gradient_vertical(W, H, DARK_NAVY, DEEP_BLUE) # Try to load background if exists try: if os.path.exists('background.png'): bg = Image.open('background.png').convert('RGB') bg = bg.resize((W, H)) # Blend background with gradient (50% opacity) img = Image.blend(img, bg, 0.3) except: pass draw = ImageDraw.Draw(img) # Load fonts font_title = get_font(70) font_name = get_font(90) font_header = get_font(50) font_large = get_font(42) font_medium = get_font(36) font_small = get_font(28) font_tiny = get_font(24) # === LEFT PANEL: Player Image & Basic Info === left_panel_x = 60 left_panel_y = 80 left_panel_w = 520 left_panel_h = 920 # Panel background draw.rounded_rectangle( [left_panel_x, left_panel_y, left_panel_x + left_panel_w, left_panel_y + left_panel_h], radius=20, fill=MEDIUM_BLUE ) # Player image container img_container_y = left_panel_y + 30 img_container_h = 480 draw.rounded_rectangle( [left_panel_x + 30, img_container_y, left_panel_x + left_panel_w - 30, img_container_y + img_container_h], radius=15, fill=DEEP_BLUE ) # Load and display player image try: player_img_response = urlopen(info['imageFront']) player_img = Image.open(player_img_response).convert('RGBA') # Calculate aspect ratio fit max_w = left_panel_w - 80 max_h = img_container_h - 40 ratio = min(max_w / player_img.width, max_h / player_img.height) new_w = int(player_img.width * ratio) new_h = int(player_img.height * ratio) player_img = player_img.resize((new_w, new_h), Image.Resampling.LANCZOS) # Center the image paste_x = left_panel_x + (left_panel_w - new_w) // 2 paste_y = img_container_y + (img_container_h - new_h) // 2 # Convert img to RGBA for pasting img_rgba = img.convert('RGBA') img_rgba.paste(player_img, (paste_x, paste_y), player_img) img = img_rgba.convert('RGB') draw = ImageDraw.Draw(img) except: pass # Rating badge (circular) badge_y = img_container_y + img_container_h + 30 badge_center_x = left_panel_x + left_panel_w // 2 badge_radius = 70 draw.ellipse( [badge_center_x - badge_radius, badge_y - badge_radius, badge_center_x + badge_radius, badge_y + badge_radius], fill=RED, outline=GOLD, width=6 ) # Rating text rating = info['attributes'][0]['value'] bbox = draw.textbbox((0, 0), rating, font=font_name) text_w = bbox[2] - bbox[0] text_h = bbox[3] - bbox[1] draw.text( (badge_center_x - text_w // 2, badge_y - text_h // 2 - 5), rating, font=font_name, fill=WHITE ) # Position and rarity info_y = badge_y + badge_radius + 40 position_text = f"{info['position']} • {info['rarity']}" bbox = draw.textbbox((0, 0), position_text, font=font_large) text_w = bbox[2] - bbox[0] draw.text( (badge_center_x - text_w // 2, info_y), position_text, font=font_large, fill=GOLD ) # Playing style style_y = info_y + 50 style_text = info['playingStyle'] bbox = draw.textbbox((0, 0), style_text, font=font_medium) text_w = bbox[2] - bbox[0] draw.text( (badge_center_x - text_w // 2, style_y), style_text, font=font_medium, fill=LIGHT_BLUE ) # Physical stats phys_y = style_y + 70 phys_stats = [ f"Height: {info['height']}cm", f"Weight: {info['weight']}kg", f"Age: {info['age']}", f"Foot: {info['foot']}" ] for stat in phys_stats: bbox = draw.textbbox((0, 0), stat, font=font_small) text_w = bbox[2] - bbox[0] draw.text( (badge_center_x - text_w // 2, phys_y), stat, font=font_small, fill=LIGHT_GRAY ) phys_y += 38 # === TOP RIGHT: Header Section === header_x = left_panel_x + left_panel_w + 60 header_y = 80 header_w = W - header_x - 60 header_h = 180 draw.rounded_rectangle( [header_x, header_y, header_x + header_w, header_y + header_h], radius=20, fill=MEDIUM_BLUE ) # Title draw.text((header_x + 40, header_y + 25), "PLAYER PROFILE", font=font_title, fill=ELECTRIC_BLUE) # Player name draw.text((header_x + 40, header_y + 100), info['playername'], font=font_name, fill=WHITE) # === MIDDLE RIGHT: Team & Info === team_y = header_y + header_h + 30 team_h = 160 draw.rounded_rectangle( [header_x, team_y, header_x + header_w, team_y + team_h], radius=20, fill=MEDIUM_BLUE ) # Team info team_info_y = team_y + 30 team_items = [ ("Team:", info['teamname']), ("League:", info['league']), ("Nation:", f"{info['nationality']} ({info['region']})") ] for label, value in team_items: draw.text((header_x + 40, team_info_y), label, font=font_medium, fill=LIGHT_BLUE) draw.text((header_x + 230, team_info_y), value, font=font_medium, fill=WHITE) team_info_y += 42 # Squad number badge squad_badge_x = header_x + header_w - 120 squad_badge_y = team_y + 30 squad_size = 100 draw.rounded_rectangle( [squad_badge_x, squad_badge_y, squad_badge_x + squad_size, squad_badge_y + squad_size], radius=15, fill=DEEP_BLUE, outline=GOLD, width=4 ) squad_num = info['squadnumber'] bbox = draw.textbbox((0, 0), squad_num, font=font_name) text_w = bbox[2] - bbox[0] text_h = bbox[3] - bbox[1] draw.text( (squad_badge_x + (squad_size - text_w) // 2, squad_badge_y + (squad_size - text_h) // 2 - 5), squad_num, font=font_name, fill=GOLD ) # === BOTTOM RIGHT: Stats Section === stats_y = team_y + team_h + 30 stats_h = H - stats_y - 80 draw.rounded_rectangle( [header_x, stats_y, header_x + header_w, stats_y + stats_h], radius=20, fill=MEDIUM_BLUE ) # Stats header draw.text((header_x + 40, stats_y + 25), "KEY ATTRIBUTES", font=font_header, fill=ELECTRIC_BLUE) # Key stats to display (top performance stats) key_stats = [ ('Offensive Awareness', info['attributes'][1]['value'], info['attributes'][1].get('booster')), ('Dribbling', info['attributes'][3]['value'], info['attributes'][3].get('booster')), ('Tight Possession', info['attributes'][4]['value'], info['attributes'][4].get('booster')), ('Ball Control', info['attributes'][2]['value'], info['attributes'][2].get('booster')), ('Acceleration', info['attributes'][21]['value'], info['attributes'][21].get('booster')), ('Speed', info['attributes'][20]['value'], info['attributes'][20].get('booster')), ('Finishing', info['attributes'][7]['value'], info['attributes'][7].get('booster')), ('Balance', info['attributes'][25]['value'], info['attributes'][25].get('booster')), ] stat_y = stats_y + 90 for stat_name, value, booster in key_stats: # Stat name draw.text((header_x + 40, stat_y), stat_name, font=font_medium, fill=LIGHT_GRAY) # Progress bar background bar_x = header_x + 380 bar_w = 500 bar_h = 28 draw.rounded_rectangle( [bar_x, stat_y + 5, bar_x + bar_w, stat_y + 5 + bar_h], radius=14, fill=DEEP_BLUE ) # Progress bar fill try: val_int = int(value) filled_w = int((val_int / 99) * bar_w) # Color based on value if val_int >= 90: bar_color = GREEN elif val_int >= 80: bar_color = LIGHT_BLUE elif val_int >= 70: bar_color = ORANGE else: bar_color = ACCENT_BLUE if filled_w > 0: draw.rounded_rectangle( [bar_x, stat_y + 5, bar_x + filled_w, stat_y + 5 + bar_h], radius=14, fill=bar_color ) except: pass # Value text value_text = value if booster: value_text = f"{value} {booster}" draw.text((bar_x + bar_w + 25, stat_y + 2), value_text, font=font_large, fill=WHITE) stat_y += 55 # Skills section skills_x = header_x + 40 skills_y = stat_y + 20 draw.text((skills_x, skills_y), "Special Skills:", font=font_medium, fill=LIGHT_BLUE) skills_y += 40 skills = info.get('playerSkills', []) skill_text = " • ".join(skills[:6]) # Top 6 skills # Word wrap for skills words = skill_text.split() lines = [] current_line = [] for word in words: test_line = ' '.join(current_line + [word]) bbox = draw.textbbox((0, 0), test_line, font=font_small) if bbox[2] - bbox[0] <= header_w - 100: current_line.append(word) else: if current_line: lines.append(' '.join(current_line)) current_line = [word] if current_line: lines.append(' '.join(current_line)) for line in lines[:2]: # Max 2 lines draw.text((skills_x, skills_y), line, font=font_small, fill=LIGHT_GRAY) skills_y += 32 # === FOOTER === footer_y = H - 50 draw.text((60, footer_y), f"ID: {player_id}", font=font_tiny, fill=LIGHT_GRAY) draw.text((W - 350, footer_y), f"Max Level: {info['maximumlevel']} • Form: {info['attributes'][28]['value']}", font=font_tiny, fill=LIGHT_GRAY) # Save to bytes img_io = io.BytesIO() img.save(img_io, 'PNG', quality=95) img_io.seek(0) return send_file(img_io, mimetype='image/png') if __name__ == '__main__': app.run(debug=True, host='0.0.0.0', port=5000)