- Created comprehensive FAZA1_GENERATION_STATUS.md with 10 categories tracking 176 tasks
- Auto-sync system for real-time progress updates
- Batch cleanup: 1411 images scanned across references/, assets/, style_test_samples/
- 33 backgrounds removed total (24 previous + 9 new)
- Updated ROADMAP.md: Visual Cleanup ✅ Complete
- Script: batch_cleanup_all_assets.py for automated processing
- Critical path identified: 17 tasks (60-80h) to Kickstarter Demo ready
183 lines
5.6 KiB
Python
183 lines
5.6 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
BATCH VISUAL ASSET CLEANUP
|
||
Removes backgrounds from ALL image assets in the project.
|
||
Processes: /references/, /assets/, /style_test_samples/
|
||
|
||
Features:
|
||
- Multi-folder scanning
|
||
- Chroma keying (green #00FF00 removal)
|
||
- Smart detection of already transparent images
|
||
- Progress tracking
|
||
- Automatic _nobg.png generation
|
||
"""
|
||
|
||
import os
|
||
from PIL import Image
|
||
import numpy as np
|
||
from pathlib import Path
|
||
|
||
# Configuration
|
||
SEARCH_FOLDERS = [
|
||
'references',
|
||
'assets/images',
|
||
'assets/bugs',
|
||
'assets/tools',
|
||
'assets/crops',
|
||
'assets/buildings',
|
||
'assets/characters',
|
||
'assets/enemies',
|
||
'assets/npcs',
|
||
'style_test_samples'
|
||
]
|
||
|
||
GREEN_KEY = (0, 255, 0) # #00FF00
|
||
TOLERANCE = 30 # Color variance tolerance
|
||
|
||
def has_transparency(image):
|
||
"""Check if image already has transparent pixels"""
|
||
if image.mode != 'RGBA':
|
||
return False
|
||
|
||
alpha = np.array(image)[:, :, 3]
|
||
return np.any(alpha < 255)
|
||
|
||
def remove_green_background(image_path, output_path):
|
||
"""Remove green chroma key background and save as transparent PNG"""
|
||
try:
|
||
img = Image.open(image_path).convert('RGBA')
|
||
data = np.array(img)
|
||
|
||
# Check if already transparent
|
||
if has_transparency(img):
|
||
print(f" ⏭️ Already transparent: {os.path.basename(image_path)}")
|
||
return False
|
||
|
||
# Extract RGB channels
|
||
red, green, blue, alpha = data[:, :, 0], data[:, :, 1], data[:, :, 2], data[:, :, 3]
|
||
|
||
# Create mask for green pixels (with tolerance)
|
||
mask = (
|
||
(np.abs(red.astype(int) - GREEN_KEY[0]) <= TOLERANCE) &
|
||
(np.abs(green.astype(int) - GREEN_KEY[1]) <= TOLERANCE) &
|
||
(np.abs(blue.astype(int) - GREEN_KEY[2]) <= TOLERANCE)
|
||
)
|
||
|
||
# Check if green background exists
|
||
green_pixel_count = np.sum(mask)
|
||
if green_pixel_count == 0:
|
||
print(f" ℹ️ No green background: {os.path.basename(image_path)}")
|
||
return False
|
||
|
||
# Set alpha to 0 for green pixels
|
||
data[:, :, 3] = np.where(mask, 0, 255)
|
||
|
||
# Save as transparent PNG
|
||
result = Image.fromarray(data, 'RGBA')
|
||
result.save(output_path, 'PNG')
|
||
|
||
percentage = (green_pixel_count / (data.shape[0] * data.shape[1])) * 100
|
||
print(f" ✅ Processed: {os.path.basename(image_path)} ({percentage:.1f}% background removed)")
|
||
return True
|
||
|
||
except Exception as e:
|
||
print(f" ❌ ERROR: {os.path.basename(image_path)} - {e}")
|
||
return False
|
||
|
||
def scan_and_process(base_dir):
|
||
"""Scan all configured folders and process images"""
|
||
stats = {
|
||
'total_found': 0,
|
||
'processed': 0,
|
||
'skipped_transparent': 0,
|
||
'skipped_no_green': 0,
|
||
'errors': 0
|
||
}
|
||
|
||
print("🔍 SCANNING FOR IMAGES...\n")
|
||
|
||
for folder in SEARCH_FOLDERS:
|
||
folder_path = os.path.join(base_dir, folder)
|
||
|
||
if not os.path.exists(folder_path):
|
||
print(f"⏭️ Skipping (not found): {folder}")
|
||
continue
|
||
|
||
print(f"\n📁 Processing: {folder}/")
|
||
print("=" * 60)
|
||
|
||
# Find all image files
|
||
image_files = []
|
||
for ext in ['*.png', '*.jpg', '*.jpeg']:
|
||
image_files.extend(Path(folder_path).rglob(ext))
|
||
|
||
folder_stats = {
|
||
'found': len(image_files),
|
||
'processed': 0
|
||
}
|
||
|
||
for img_path in sorted(image_files):
|
||
# Skip already processed files
|
||
if '_nobg' in img_path.stem:
|
||
continue
|
||
|
||
stats['total_found'] += 1
|
||
|
||
# Generate output filename
|
||
output_path = img_path.parent / f"{img_path.stem}_nobg.png"
|
||
|
||
# Skip if already exists
|
||
if output_path.exists():
|
||
print(f" ⏭️ Already exists: {output_path.name}")
|
||
stats['skipped_transparent'] += 1
|
||
continue
|
||
|
||
# Process the image
|
||
result = remove_green_background(str(img_path), str(output_path))
|
||
|
||
if result:
|
||
stats['processed'] += 1
|
||
folder_stats['processed'] += 1
|
||
elif result is False:
|
||
# Check reason from function output
|
||
stats['skipped_no_green'] += 1
|
||
|
||
print(f"\n📊 Folder Summary: {folder_stats['processed']}/{folder_stats['found']} processed")
|
||
|
||
return stats
|
||
|
||
def main():
|
||
"""Main execution"""
|
||
print("=" * 60)
|
||
print("🎨 BATCH VISUAL ASSET CLEANUP")
|
||
print("=" * 60)
|
||
print(f"Target folders: {len(SEARCH_FOLDERS)}")
|
||
print(f"Chroma key: RGB{GREEN_KEY} ±{TOLERANCE}")
|
||
print("=" * 60)
|
||
|
||
# Get project root
|
||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||
project_root = os.path.dirname(script_dir)
|
||
|
||
# Process all folders
|
||
stats = scan_and_process(project_root)
|
||
|
||
# Final report
|
||
print("\n" + "=" * 60)
|
||
print("📊 FINAL REPORT")
|
||
print("=" * 60)
|
||
print(f"Total images found: {stats['total_found']}")
|
||
print(f"✅ Successfully processed: {stats['processed']}")
|
||
print(f"⏭️ Already transparent: {stats['skipped_transparent']}")
|
||
print(f"ℹ️ No green background: {stats['skipped_no_green']}")
|
||
print(f"❌ Errors: {stats['errors']}")
|
||
print("=" * 60)
|
||
|
||
if stats['processed'] > 0:
|
||
print(f"\n✅ CLEANUP COMPLETE! {stats['processed']} backgrounds removed.")
|
||
else:
|
||
print("\n✅ All images already clean!")
|
||
|
||
if __name__ == "__main__":
|
||
main()
|