novo
This commit is contained in:
@@ -0,0 +1,254 @@
|
||||
#!/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")
|
||||
Reference in New Issue
Block a user