# utils/generators/profile.py import io from PIL import Image, ImageOps, ImageFilter, ImageDraw from .base import BaseGenerator from .legendary import PremiumGenerator from .common import CommonGenerator from .custom import CustomGenerator class ProfileGenerator(BaseGenerator): def build_profile(self, username: str, stats: dict, showcase_bytes: bytes = None, team_data: list = None, avatar_bytes: bytes = None) -> io.BytesIO: card_w, card_h = 580, 820 gap = 10 margin = 15 columns, rows = 4, 2 padding = 40 W = (margin * 2) + (card_w * columns) + (gap * (columns - 1)) H = (margin * 2) + (card_h * rows) + (gap * (rows - 1)) canvas = Image.new("RGBA", (W, H), (0, 0, 0, 255)) cleanup = [canvas] try: shadow = Image.new("RGBA", (card_w + 20, card_h + 20), (0, 0, 0, 0)) shadow_draw = ImageDraw.Draw(shadow) shadow_draw.rectangle([4, 4, card_w + 16, card_h + 16], fill=(0, 0, 0, 180)) pre_rendered_shadow = shadow.filter(ImageFilter.GaussianBlur(12)) cleanup.append(pre_rendered_shadow) if team_data: # ⚡ THE FIX: Filter out empty slots so cards automatically shift left and fill gaps! active_cards = [card for card in team_data if card and card.get('bytes')] for i, t_char in enumerate(active_cards): if i >= 8: break # Max 8 cards on the grid col = i % columns row = i // columns inner_x = margin + col * (card_w + gap) inner_y = margin + row * (card_h + gap) try: if t_char.get('custom_frame_url') and t_char.get('frame_bytes'): generator = CustomGenerator() full_card = generator.build_template(t_char['bytes'], t_char['frame_bytes'], t_char['name']) elif t_char.get('rarity') in ["SSR", "UR"]: generator = PremiumGenerator() full_card = generator.build_template(t_char['bytes'], t_char.get('element', 'None')) else: generator = CommonGenerator() full_card = generator.build_template(t_char['bytes'], t_char.get('element', 'None')) cleanup.append(full_card) text_io = generator.apply_text( full_card, t_char.get('name', 'Unknown'), t_char.get('print_num', 1), t_char.get('element', 'None'), t_char.get('rarity', 'C') ) cleanup.append(text_io) finished_card = Image.open(text_io).convert("RGBA") cleanup.append(finished_card) if finished_card.size == (660, 900): cropped_card = finished_card.crop(( padding, padding, finished_card.width - padding, finished_card.height - padding )) else: cropped_card = finished_card cleanup.append(cropped_card) if cropped_card.size != (card_w, card_h): cropped_card = cropped_card.resize((card_w, card_h), Image.Resampling.LANCZOS) cleanup.append(cropped_card) canvas.paste(pre_rendered_shadow, (inner_x - 6, inner_y - 6), pre_rendered_shadow) canvas.paste(cropped_card, (inner_x, inner_y), cropped_card) except Exception as e: print(f"Failed to build card {i}: {e}") buf = io.BytesIO() canvas.save(buf, format="WEBP", quality=95) buf.seek(0) return buf finally: for img in cleanup: try: img.close() except: pass