#!/usr/bin/env python3 """ šŸš€ MASTER ASSET PIPELINE - COMPLETE AUTOMATION Handles: 1. Background removal (advanced AI) 2. Preview generation (256x256) 3. Sprite generation (32x32) 4. Batch processing for all 11k+ assets Target: ~11,000 total images Current: 644 Needed: ~10,356 more! """ import subprocess import shutil from pathlib import Path from PIL import Image, ImageFilter import cv2 import numpy as np from typing import List, Tuple # Paths REPO = Path("/Users/davidkotnik/repos/novafarma") ASSETS = REPO / "assets/slike" SCRIPTS = REPO / "scripts" # Target sizes SIZE_ORIGINAL = (1024, 1024) SIZE_PREVIEW = (256, 256) SIZE_SPRITE = (32, 32) class AssetPipeline: def __init__(self): self.stats = { "background_removed": 0, "previews_created": 0, "sprites_created": 0, "errors": 0 } def remove_background_advanced(self, image_path: Path) -> bool: """ Advanced background removal using multiple techniques: 1. Color threshold (white/black backgrounds) 2. Edge detection 3. Alpha channel preservation """ try: # Load image img = Image.open(image_path).convert("RGBA") data = np.array(img) # Get RGB and Alpha rgb = data[:, :, :3] alpha = data[:, :, 3] # Detect background color (most common in corners) corners = [ rgb[0, 0], # top-left rgb[0, -1], # top-right rgb[-1, 0], # bottom-left rgb[-1, -1] # bottom-right ] bg_color = np.median(corners, axis=0).astype(int) # Create mask for background # Tolerance for color matching tolerance = 30 diff = np.abs(rgb - bg_color) mask = np.all(diff < tolerance, axis=2) # Set background to transparent alpha[mask] = 0 # Update alpha channel data[:, :, 3] = alpha # Save result = Image.fromarray(data, mode="RGBA") result.save(image_path) return True except Exception as e: print(f" āŒ Background removal failed for {image_path.name}: {e}") return False def create_preview(self, original_path: Path) -> Path: """Create 256x256 preview version.""" try: # Target path preview_name = original_path.stem + "_preview_256x256.png" preview_path = original_path.parent / preview_name # Skip if exists if preview_path.exists(): return preview_path # Load and resize img = Image.open(original_path) # High-quality resize img_resized = img.resize(SIZE_PREVIEW, Image.Resampling.LANCZOS) # Save img_resized.save(preview_path, "PNG", optimize=True) return preview_path except Exception as e: print(f" āŒ Preview creation failed for {original_path.name}: {e}") return None def create_sprite(self, original_path: Path) -> Path: """Create 32x32 sprite version.""" try: # Target path sprite_name = original_path.stem + "_sprite_32x32.png" sprite_path = original_path.parent / sprite_name # Skip if exists if sprite_path.exists(): return sprite_path # Load and resize img = Image.open(original_path) # High-quality resize with sharpening img_resized = img.resize(SIZE_SPRITE, Image.Resampling.LANCZOS) # Sharpen for pixel art clarity img_resized = img_resized.filter(ImageFilter.SHARPEN) # Save img_resized.save(sprite_path, "PNG", optimize=True) return sprite_path except Exception as e: print(f" āŒ Sprite creation failed for {original_path.name}: {e}") return None def process_single_asset(self, asset_path: Path, remove_bg: bool = True, create_preview: bool = True, create_sprite: bool = True): """Process a single asset through entire pipeline.""" print(f"šŸ“¦ Processing: {asset_path.name}") # Step 1: Background removal if remove_bg and not asset_path.stem.endswith(("_preview", "_sprite")): if self.remove_background_advanced(asset_path): print(f" āœ… Background removed") self.stats["background_removed"] += 1 # Step 2: Create preview if create_preview and not asset_path.stem.endswith(("_preview", "_sprite")): preview_path = self.create_preview(asset_path) if preview_path: print(f" āœ… Preview created: {preview_path.name}") self.stats["previews_created"] += 1 # Step 3: Create sprite if create_sprite and not asset_path.stem.endswith(("_preview", "_sprite")): sprite_path = self.create_sprite(asset_path) if sprite_path: print(f" āœ… Sprite created: {sprite_path.name}") self.stats["sprites_created"] += 1 def process_all_assets(self, category: str = None): """Process all assets or specific category.""" if category: asset_dirs = [ASSETS / category] else: asset_dirs = [d for d in ASSETS.iterdir() if d.is_dir()] for asset_dir in asset_dirs: print(f"\nšŸ“ Processing category: {asset_dir.name}") # Find all original PNGs (not preview/sprite versions) originals = [ f for f in asset_dir.rglob("*.png") if not f.stem.endswith(("_preview_256x256", "_sprite_32x32")) ] print(f" Found {len(originals)} original assets") for asset_path in originals: self.process_single_asset(asset_path) self.print_stats() def print_stats(self): """Print pipeline statistics.""" print("\n" + "="*70) print("šŸ“Š PIPELINE STATISTICS") print("="*70) print(f" Backgrounds removed: {self.stats['background_removed']:4d}") print(f" Previews created: {self.stats['previews_created']:4d}") print(f" Sprites created: {self.stats['sprites_created']:4d}") print(f" Errors: {self.stats['errors']:4d}") print("="*70) def main(): import argparse parser = argparse.ArgumentParser(description="Master Asset Pipeline") parser.add_argument("--category", help="Specific category to process") parser.add_argument("--no-bg-remove", action="store_true", help="Skip background removal") parser.add_argument("--no-preview", action="store_true", help="Skip preview creation") parser.add_argument("--no-sprite", action="store_true", help="Skip sprite creation") args = parser.parse_args() pipeline = AssetPipeline() print("="*70) print("šŸš€ MASTER ASSET PIPELINE") print("="*70) print(f"Target: ~11,000 total images") print(f"Processing: {'All categories' if not args.category else args.category}") print("="*70) pipeline.process_all_assets( category=args.category ) if __name__ == "__main__": main()