255 lines
8.0 KiB
Python
255 lines
8.0 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Sprite Cleanup & Optimization Tool for Novafarma
|
|
Fixes transparency issues and optimizes file sizes
|
|
|
|
Features:
|
|
- Removes checkerboard backgrounds
|
|
- Converts to proper transparent PNG
|
|
- Optimizes file size (500KB+ → 50-100KB)
|
|
- Batch processing all sprites
|
|
|
|
Usage:
|
|
python sprite_cleanup.py
|
|
"""
|
|
|
|
from PIL import Image
|
|
import os
|
|
import glob
|
|
|
|
def remove_checkerboard_background(image):
|
|
"""
|
|
Remove checkerboard pattern (common in AI-generated images)
|
|
Target: Gray/white checkerboard (R,G,B values between 150-255 and similar)
|
|
"""
|
|
img = image.convert('RGBA')
|
|
data = img.getdata()
|
|
|
|
new_data = []
|
|
for item in data:
|
|
r, g, b, a = item
|
|
|
|
# Detect checkerboard gray/white
|
|
# Checkerboard is usually perfect gray (R≈G≈B) with high values
|
|
is_gray = abs(r - g) < 30 and abs(g - b) <30
|
|
is_light = r > 150 and g > 150 and b > 150
|
|
|
|
if is_gray and is_light:
|
|
# Make transparent
|
|
new_data.append((r, g, b, 0))
|
|
else:
|
|
# Keep original
|
|
new_data.append(item)
|
|
|
|
img.putdata(new_data)
|
|
return img
|
|
|
|
def remove_white_background(image):
|
|
"""Remove pure white or near-white backgrounds"""
|
|
img = image.convert('RGBA')
|
|
data = img.getdata()
|
|
|
|
new_data = []
|
|
for item in data:
|
|
r, g, b, a = item
|
|
|
|
# Near-white threshold
|
|
if r > 240 and g > 240 and b > 240:
|
|
new_data.append((r, g, b, 0))
|
|
else:
|
|
new_data.append(item)
|
|
|
|
img.putdata(new_data)
|
|
return img
|
|
|
|
def remove_solid_color_background(image, target_color=(200, 200, 200), tolerance=30):
|
|
"""Remove specific solid color background with tolerance"""
|
|
img = image.convert('RGBA')
|
|
data = img.getdata()
|
|
|
|
target_r, target_g, target_b = target_color
|
|
new_data = []
|
|
|
|
for item in data:
|
|
r, g, b, a = item
|
|
|
|
# Check if color is within tolerance of target
|
|
if (abs(r - target_r) < tolerance and
|
|
abs(g - target_g) < tolerance and
|
|
abs(b - target_b) < tolerance):
|
|
new_data.append((r, g, b, 0))
|
|
else:
|
|
new_data.append(item)
|
|
|
|
img.putdata(new_data)
|
|
return img
|
|
|
|
def optimize_sprite(input_file, output_file=None, aggressive=True):
|
|
"""
|
|
Optimize sprite:
|
|
1. Remove backgrounds
|
|
2. Reduce file size
|
|
3. Save as optimized PNG
|
|
"""
|
|
|
|
print(f"📦 Processing: {input_file}")
|
|
|
|
if not os.path.exists(input_file):
|
|
print(f"❌ File not found: {input_file}")
|
|
return False
|
|
|
|
# Original file size
|
|
original_size = os.path.getsize(input_file) / 1024 # KB
|
|
|
|
try:
|
|
# Load image
|
|
img = Image.open(input_file)
|
|
|
|
# Get original dimensions
|
|
width, height = img.size
|
|
|
|
# Remove backgrounds (multiple passes for best results)
|
|
img = remove_checkerboard_background(img)
|
|
img = remove_white_background(img)
|
|
|
|
# Optional: Resize if too large (AI images are often huge)
|
|
if aggressive and (width > 512 or height > 512):
|
|
# Calculate new size (max 512px on longest side)
|
|
ratio = min(512 / width, 512 / height)
|
|
new_width = int(width * ratio)
|
|
new_height = int(height * ratio)
|
|
|
|
print(f" ⚙️ Resizing: {width}x{height} → {new_width}x{new_height}")
|
|
img = img.resize((new_width, new_height), Image.LANCZOS)
|
|
|
|
# Output filename
|
|
if output_file is None:
|
|
# Create backup
|
|
backup_file = input_file.replace('.png', '_backup.png')
|
|
if not os.path.exists(backup_file):
|
|
os.rename(input_file, backup_file)
|
|
print(f" 💾 Backup saved: {backup_file}")
|
|
output_file = input_file
|
|
|
|
# Save optimized
|
|
img.save(output_file, 'PNG', optimize=True, compress_level=9)
|
|
|
|
# New file size
|
|
new_size = os.path.getsize(output_file) / 1024 # KB
|
|
reduction = ((original_size - new_size) / original_size) * 100
|
|
|
|
print(f" ✅ Saved: {output_file}")
|
|
print(f" 📊 Size: {original_size:.1f}KB → {new_size:.1f}KB ({reduction:.1f}% reduction)")
|
|
|
|
return True
|
|
|
|
except Exception as e:
|
|
print(f" ❌ Error: {e}")
|
|
return False
|
|
|
|
def batch_optimize_directory(directory, pattern='*.png', aggressive=True):
|
|
"""Optimize all PNG files in a directory"""
|
|
|
|
files = glob.glob(os.path.join(directory, pattern))
|
|
|
|
if not files:
|
|
print(f"No files found matching {pattern} in {directory}")
|
|
return
|
|
|
|
print(f"\n🎨 Optimizing {len(files)} files in {directory}")
|
|
print("=" * 60)
|
|
|
|
success_count = 0
|
|
total_original_size = 0
|
|
total_new_size = 0
|
|
|
|
for file in files:
|
|
# Skip backups
|
|
if '_backup' in file or '_strip' in file:
|
|
print(f"⏭️ Skipping: {os.path.basename(file)}")
|
|
continue
|
|
|
|
original_size = os.path.getsize(file) / 1024
|
|
|
|
if optimize_sprite(file, aggressive=aggressive):
|
|
success_count += 1
|
|
new_size = os.path.getsize(file) / 1024
|
|
total_original_size += original_size
|
|
total_new_size += new_size
|
|
|
|
print() # Blank line between files
|
|
|
|
# Summary
|
|
print("=" * 60)
|
|
print(f"✅ Successfully processed: {success_count}/{len(files)} files")
|
|
if total_original_size > 0:
|
|
total_reduction = ((total_original_size - total_new_size) / total_original_size) * 100
|
|
print(f"📊 Total size: {total_original_size:.1f}KB → {total_new_size:.1f}KB")
|
|
print(f"💾 Space saved: {total_original_size - total_new_size:.1f}KB ({total_reduction:.1f}%)")
|
|
|
|
def create_optimized_player_sprite():
|
|
"""Create a simple, clean player sprite (placeholder)"""
|
|
|
|
print("\n🎨 Creating optimized player sprite...")
|
|
|
|
# 64x64 pixel art player
|
|
size = 64
|
|
img = Image.new('RGBA', (size, size), (0, 0, 0, 0))
|
|
pixels = img.load()
|
|
|
|
# Simple character (head + body + legs)
|
|
# Head (circle)
|
|
for y in range(20, 35):
|
|
for x in range(25, 40):
|
|
dx = x - 32
|
|
dy = y - 27
|
|
if dx*dx + dy*dy < 64: # Circle radius
|
|
pixels[x, y] = (255, 200, 150, 255) # Skin tone
|
|
|
|
# Body (rectangle)
|
|
for y in range(35, 50):
|
|
for x in range(27, 37):
|
|
pixels[x, y] = (100, 150, 200, 255) # Blue shirt
|
|
|
|
# Legs (two rectangles)
|
|
for y in range(50, 64):
|
|
for x in range(27, 32):
|
|
pixels[x, y] = (50, 50, 100, 255) # Dark pants
|
|
for x in range(32, 37):
|
|
pixels[x, y] = (50, 50, 100, 255)
|
|
|
|
output_file = 'player_sprite_clean.png'
|
|
img.save(output_file, 'PNG', optimize=True)
|
|
print(f"✅ Created: {output_file}")
|
|
|
|
return output_file
|
|
|
|
if __name__ == '__main__':
|
|
print("🎮 Novafarma Sprite Cleanup Tool\n")
|
|
|
|
# Option 1: Clean all sprites in assets folder
|
|
assets_dir = 'c:/novafarma/assets'
|
|
|
|
if os.path.exists(assets_dir):
|
|
print("🔧 Mode: Aggressive optimization (resize + cleanup)")
|
|
print("⚠️ Backups will be created automatically\n")
|
|
|
|
# Process main assets folder
|
|
batch_optimize_directory(assets_dir, '*.png', aggressive=True)
|
|
|
|
# Process sprites subfolder
|
|
sprites_dir = os.path.join(assets_dir, 'sprites')
|
|
if os.path.exists(sprites_dir):
|
|
batch_optimize_directory(sprites_dir, '*.png', aggressive=False) # Don't resize sprite sheets
|
|
|
|
else:
|
|
print(f"❌ Assets directory not found: {assets_dir}")
|
|
print("\n💡 Usage:")
|
|
print(" 1. Update assets_dir path in script")
|
|
print(" 2. Run: python sprite_cleanup.py")
|
|
print("\n Or use individual sprite:")
|
|
print(" optimize_sprite('path/to/sprite.png')")
|
|
|
|
print("\n✨ Done! Check your assets folder for optimized sprites.")
|
|
print("📁 Backups are saved as *_backup.png")
|