import os, sys, argparse # Dependency Check try: from PIL import Image, ImageOps except ImportError: print("❌ Error: Pillow library not found.") print("👉 Please run: pip install Pillow") sys.exit(1) # Configuration # Paths are relative to where the script is run, assuming run from root or automation folder BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) SOURCE_DIR = os.path.join(BASE_DIR, "Raw_Assets") OUTPUT_DIR = os.path.join(BASE_DIR, "Ready_Assets") # Target Dimensions (Width, Height) ASSETS = { "AppIcon": (1024, 1024), "logo_header": (300, 80), "scanner_frame": (600, 800), "empty_library": (800, 800), "share_watermark": (400, 100), "card_placeholder": (600, 840), } # Assets whose target aspect ratio differs significantly from a typical AI square output. # These use thumbnail+pad (letterbox) instead of center-crop to preserve full image content. PAD_ASSETS = {"logo_header", "share_watermark"} def resize_fit(img, size): """Center-crop and scale to exact size. Best when source matches target aspect ratio.""" return ImageOps.fit(img, size, method=Image.Resampling.LANCZOS) def resize_pad(img, size, bg_color=(0, 0, 0, 0)): """Scale to fit within size, then pad with bg_color to reach exact dimensions. Preserves the full image — nothing is cropped. Ideal for logos and banners.""" img.thumbnail(size, Image.Resampling.LANCZOS) canvas = Image.new("RGBA", size, bg_color) offset = ((size[0] - img.width) // 2, (size[1] - img.height) // 2) canvas.paste(img, offset) return canvas def process_assets(force_pad=False, force_crop=False): print(f"📂 Working Directory: {BASE_DIR}") # Create directories if they don't exist if not os.path.exists(SOURCE_DIR): os.makedirs(SOURCE_DIR) print(f"✅ Created source folder: {SOURCE_DIR}") print("👉 ACTION REQUIRED: Place your raw AI images here (e.g., 'AppIcon.png') and run this script again.") return if not os.path.exists(OUTPUT_DIR): os.makedirs(OUTPUT_DIR) print(f"✅ Created output folder: {OUTPUT_DIR}") print(f"🚀 Processing images from 'Raw_Assets'...") for name, size in ASSETS.items(): found = False # Check for common image extensions for ext in [".png", ".jpg", ".jpeg", ".webp"]: source_path = os.path.join(SOURCE_DIR, name + ext) if os.path.exists(source_path): try: img = Image.open(source_path) if img.mode != 'RGBA': img = img.convert('RGBA') # Choose resize strategy: # - PAD_ASSETS (and --pad flag) use thumbnail+letterbox to avoid cropping banner images. # - Everything else uses center-crop (ImageOps.fit) for a clean full-bleed fill. use_pad = (name in PAD_ASSETS or force_pad) and not force_crop if use_pad: processed_img = resize_pad(img, size) mode_label = "pad" else: processed_img = resize_fit(img, size) mode_label = "crop" output_path = os.path.join(OUTPUT_DIR, name + ".png") processed_img.save(output_path, "PNG") print(f"✅ Generated: {name}.png ({size[0]}x{size[1]}, {mode_label})") found = True break except Exception as e: print(f"❌ Error processing {name}: {e}") if not found: print(f"⚠️ Skipped: {name} (File not found in Raw_Assets)") if __name__ == "__main__": parser = argparse.ArgumentParser(description="Resize raw AI assets to Xcode-ready dimensions.") group = parser.add_mutually_exclusive_group() group.add_argument("--pad", action="store_true", help="Force thumbnail+pad for ALL assets (no cropping).") group.add_argument("--crop", action="store_true", help="Force center-crop for ALL assets (overrides default pad assets).") args = parser.parse_args() process_assets(force_pad=args.pad, force_crop=args.crop)