#!/usr/bin/env python3 """ Master Asset Processor - Mrtva Dolina Processes all game assets: - Remove green screen (#00FF00) โ†’ transparent - Separate multi-object images - Organize into clean structure """ import os import json from pathlib import Path from PIL import Image import numpy as np from scipy import ndimage import shutil class MrtvaDolina_AssetProcessor: def __init__(self, base_dir="/Users/davidkotnik/Desktop/novafarma"): self.base_dir = Path(base_dir) self.output_dir = self.base_dir / "mrtva_dolina" # Green screen settings self.green_color = np.array([0, 255, 0]) self.tolerance = 30 self.min_object_size = 400 # minimum 20x20 pixels # Source directories (prioritize green screen sources) self.sources_greenscreen = [ self.base_dir / "Slike_za_Tiled", # Has green screen ] self.sources_transparent = [ self.base_dir / "assets" / "narezano_loceno", # Already transparent ] # Category mapping (keywords โ†’ category) self.categories = { "characters/players": ["kai_character", "ana_character", "player"], "characters/npcs": ["npc", "ivan", "marija", "trader", "blacksmith", "merchant", "baker", "farmer", "doctor", "romance"], "characters/zombies": ["zombie"], "creatures/animals": ["animal", "cow", "chicken", "dog", "livestock", "donkey", "horse"], "creatures/monsters": ["troll", "creature", "anomalous", "cryptid", "slime"], "creatures/bosses": ["grok", "werewolf", "boss"], "buildings/houses": ["house", "farmhouse", "barn"], "buildings/ruins": ["ruin", "buildings_ruins"], "buildings/structures": ["building", "town_buildings", "structure", "blacksmith_workshop", "bridge", "wall", "city", "portal"], "terrain/ground": ["grass", "soil", "tileset", "ground", "terrain"], "terrain/water": ["water", "ocean", "pond", "river"], "terrain/special": ["mine_tileset", "mine_interior", "mine_entrance"], "vegetation/trees": ["tree", "sequoia", "birch", "vegetation"], "vegetation/plants": ["flower", "plant", "leaf"], "vegetation/crops": ["crop", "seed", "wheat", "vegetable", "fruit"], "objects/farming": ["sprinkler", "tool", "farming"], "objects/items": ["items_pack", "weapon", "bow", "arrow", "backpack", "chest", "grave"], "objects/furniture": ["furniture", "bathroom", "bedroom", "kitchen", "living_room"], } # Statistics self.stats = { "processed": 0, "separated": 0, "skipped": 0, "errors": 0 } # Metadata collection self.metadata = [] def create_output_structure(self): """Create all category directories""" print("๐Ÿ—‚๏ธ Creating directory structure...") for category in self.categories.keys(): category_path = self.output_dir / category category_path.mkdir(parents=True, exist_ok=True) print(f" โœ“ {category}") # Also create misc category (self.output_dir / "misc").mkdir(parents=True, exist_ok=True) print(f" โœ“ misc") def is_green_screen(self, img_array): """Check if image has green screen background""" if img_array.shape[2] < 3: return False # Check for green pixels green_mask = np.all(np.abs(img_array[:, :, :3] - self.green_color) < self.tolerance, axis=2) green_percentage = np.sum(green_mask) / (img_array.shape[0] * img_array.shape[1]) return green_percentage > 0.1 # At least 10% green def remove_green_screen(self, img): """Remove green screen and make transparent""" img_array = np.array(img.convert("RGBA")) # Create mask for green pixels green_mask = np.all(np.abs(img_array[:, :, :3] - self.green_color) < self.tolerance, axis=2) # Set alpha to 0 for green pixels img_array[green_mask, 3] = 0 return Image.fromarray(img_array) def separate_objects(self, img): """Detect and separate individual objects""" img_array = np.array(img) # Get alpha channel (transparency) if img_array.shape[2] == 4: alpha = img_array[:, :, 3] else: # No alpha channel, use luminance threshold gray = np.mean(img_array[:, :, :3], axis=2) alpha = (gray > 10).astype(np.uint8) * 255 # Label connected components labels, num_objects = ndimage.label(alpha > 128) if num_objects <= 1: return [img] # Single object or empty # Extract each object objects = [] for i in range(1, num_objects + 1): mask = (labels == i) # Check object size if np.sum(mask) < self.min_object_size: continue # Find bounding box rows = np.any(mask, axis=1) cols = np.any(mask, axis=0) if not np.any(rows) or not np.any(cols): continue y_min, y_max = np.where(rows)[0][[0, -1]] x_min, x_max = np.where(cols)[0][[0, -1]] # Crop object cropped = img_array[y_min:y_max+1, x_min:x_max+1] objects.append(Image.fromarray(cropped)) return objects if objects else [img] def categorize_filename(self, filename): """Determine category from filename""" filename_lower = filename.lower() for category, keywords in self.categories.items(): if any(keyword in filename_lower for keyword in keywords): return category return "misc" def process_image(self, image_path, test_mode=False, has_greenscreen=True): """Process a single image""" try: # Load image img = Image.open(image_path) if has_greenscreen: # Check and remove green screen img_array = np.array(img.convert("RGBA")) if not self.is_green_screen(img_array): if test_mode: print(f" โŠ˜ {image_path.name} - No green screen, skipping") self.stats["skipped"] += 1 return # Remove green screen img_transparent = self.remove_green_screen(img) else: # Already transparent, just convert to RGBA img_transparent = img.convert("RGBA") # Separate objects objects = self.separate_objects(img_transparent) # Categorize category = self.categorize_filename(image_path.stem) # Save objects if len(objects) == 1: # Single object output_path = self.output_dir / category / f"{image_path.stem}.png" objects[0].save(output_path, "PNG") if test_mode: print(f" โœ“ {image_path.name} โ†’ {category}/{output_path.name}") self.metadata.append({ "filename": output_path.name, "category": category, "source": str(image_path), "objects": 1 }) else: # Multiple objects for idx, obj in enumerate(objects, 1): output_path = self.output_dir / category / f"{image_path.stem}_obj{idx:02d}.png" obj.save(output_path, "PNG") if test_mode: print(f" โœ“ {image_path.name} โ†’ {category}/ ({len(objects)} objects)") self.metadata.append({ "filename": f"{image_path.stem}_obj*.png", "category": category, "source": str(image_path), "objects": len(objects) }) self.stats["separated"] += len(objects) self.stats["processed"] += 1 except Exception as e: print(f" โœ— ERROR: {image_path.name} - {str(e)}") self.stats["errors"] += 1 def test_run(self, num_samples=10): """Test processing on a few images""" print(f"\n๐Ÿงช TEST RUN - Processing {num_samples} sample images\n") # Collect sample images from GREENSCREEN sources samples = [] for source in self.sources_greenscreen: if source.exists(): png_files = list(source.glob("**/*.png"))[:num_samples] samples.extend(png_files) samples = samples[:num_samples] print(f"Found {len(samples)} test images from green screen sources\n") # Process samples for img_path in samples: self.process_image(img_path, test_mode=True, has_greenscreen=True) # Show stats print(f"\n๐Ÿ“Š Test Results:") print(f" Processed: {self.stats['processed']}") print(f" Separated: {self.stats['separated']}") print(f" Skipped: {self.stats['skipped']}") print(f" Errors: {self.stats['errors']}") return self.stats["errors"] == 0 def process_all(self): """Process all images""" print("\n๐Ÿš€ PROCESSING ALL IMAGES\n") # Reset stats self.stats = {"processed": 0, "separated": 0, "skipped": 0, "errors": 0} # Process green screen images print("๐Ÿ“— Processing GREEN SCREEN images (Slike_za_Tiled)...") greenscreen_images = [] for source in self.sources_greenscreen: if source.exists(): png_files = list(source.glob("**/*.png")) greenscreen_images.extend(png_files) print(f" Found {len(png_files)} images in {source.name}") for idx, img_path in enumerate(greenscreen_images, 1): if idx % 100 == 0: print(f" Progress: {idx}/{len(greenscreen_images)} ({idx/len(greenscreen_images)*100:.1f}%)") self.process_image(img_path, has_greenscreen=True) print(f" โœ“ Processed {len(greenscreen_images)} green screen images\n") # Process transparent images (copy & categorize) print("๐Ÿ“˜ Processing TRANSPARENT images (narezano_loceno)...") transparent_images = [] for source in self.sources_transparent: if source.exists(): png_files = list(source.glob("**/*.png")) transparent_images.extend(png_files) print(f" Found {len(png_files)} images in {source.name}") for idx, img_path in enumerate(transparent_images, 1): if idx % 100 == 0: print(f" Progress: {idx}/{len(transparent_images)} ({idx/len(transparent_images)*100:.1f}%)") self.process_image(img_path, has_greenscreen=False) print(f" โœ“ Processed {len(transparent_images)} transparent images\n") total = len(greenscreen_images) + len(transparent_images) print(f"\nโœ… DONE! Processed {total} images") print(f"\n๐Ÿ“Š Final Statistics:") print(f" Processed: {self.stats['processed']}") print(f" Separated: {self.stats['separated']}") print(f" Skipped: {self.stats['skipped']}") print(f" Errors: {self.stats['errors']}") # Save metadata metadata_path = self.output_dir / "metadata.json" with open(metadata_path, 'w') as f: json.dump(self.metadata, f, indent=2) print(f"\n๐Ÿ’พ Metadata saved to: {metadata_path}") if __name__ == "__main__": print("=" * 60) print(" MASTER ASSET PROCESSOR - MRTVA DOLINA") print("=" * 60) processor = MrtvaDolina_AssetProcessor() # Create directory structure processor.create_output_structure() # Test run first print("\n" + "=" * 60) success = processor.test_run(num_samples=10) if success: print("\nโœ… Test run successful!") print("\n" + "=" * 60) print("STARTING FULL PROCESSING...") print("=" * 60) # Run full processing processor.process_all() print("\n" + "=" * 60) print("๐ŸŽ‰ ALL DONE!") print("=" * 60) else: print("\nโŒ Test run had errors. Please fix before continuing.")