204 lines
6.4 KiB
Python
204 lines
6.4 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
PSD Style Render Comparison Tool
|
|
=================================
|
|
Compares Photopea reference renders against our engine's compositing output.
|
|
|
|
Usage:
|
|
python3 tools/compare_renders.py [--test-dir DIR] [--run-engine]
|
|
|
|
Requires: imagemagick (compare, identify commands)
|
|
"""
|
|
|
|
import subprocess
|
|
import os
|
|
import sys
|
|
import json
|
|
|
|
TEST_DIR = "/home/hc/Pictures/_psd_stil_test"
|
|
FX_PREFIX = "fx_"
|
|
|
|
|
|
def find_test_files(test_dir):
|
|
"""Find all fx_ PSD files and their reference PNGs."""
|
|
tests = []
|
|
for f in sorted(os.listdir(test_dir)):
|
|
if f.endswith('.psd') and f.startswith(FX_PREFIX):
|
|
name = f[:-4] # e.g. "fx_drop_shadow"
|
|
psd_path = os.path.join(test_dir, f)
|
|
ref_path = os.path.join(test_dir, f"{name}_reference.png")
|
|
out_path = os.path.join(test_dir, f"{name}_engine.png")
|
|
diff_path = os.path.join(test_dir, f"{name}_diff.png")
|
|
tests.append({
|
|
'name': name,
|
|
'psd': psd_path,
|
|
'reference': ref_path,
|
|
'engine': out_path,
|
|
'diff': diff_path,
|
|
})
|
|
return tests
|
|
|
|
|
|
def check_imagemagick():
|
|
"""Verify ImageMagick is available."""
|
|
try:
|
|
subprocess.run(['identify', '--version'], capture_output=True, check=True)
|
|
return True
|
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
print("ERROR: ImageMagick not found. Install with: sudo apt install imagemagick")
|
|
return False
|
|
|
|
|
|
def pixel_compare(ref_path, out_path):
|
|
"""Compare two images using ImageMagick, return statistics."""
|
|
if not os.path.exists(ref_path) or not os.path.exists(out_path):
|
|
return None
|
|
|
|
# Get dimensions
|
|
result = subprocess.run(
|
|
['identify', '-format', '%wx%h', ref_path],
|
|
capture_output=True, text=True
|
|
)
|
|
ref_size = result.stdout.strip()
|
|
|
|
result = subprocess.run(
|
|
['identify', '-format', '%wx%h', out_path],
|
|
capture_output=True, text=True
|
|
)
|
|
out_size = result.stdout.strip()
|
|
|
|
if ref_size != out_size:
|
|
return {'error': f'Size mismatch: ref={ref_size} engine={out_size}'}
|
|
|
|
# Use ImageMagick compare with AE (Absolute Error count)
|
|
result = subprocess.run(
|
|
['compare', '-metric', 'AE', ref_path, out_path, 'null:'],
|
|
capture_output=True, text=True
|
|
)
|
|
ae = int(result.stderr.strip()) if result.stderr.strip().isdigit() else -1
|
|
|
|
# Use RMSE
|
|
result = subprocess.run(
|
|
['compare', '-metric', 'RMSE', ref_path, out_path, 'null:'],
|
|
capture_output=True, text=True
|
|
)
|
|
rmse_str = result.stderr.strip()
|
|
try:
|
|
rmse = float(rmse_str.split('(')[1].split(')')[0]) if '(' in rmse_str else float(rmse_str)
|
|
except (ValueError, IndexError):
|
|
rmse = -1.0
|
|
|
|
# Generate diff image
|
|
diff_path = out_path.replace('_engine.png', '_diff.png')
|
|
subprocess.run(
|
|
['compare', ref_path, out_path, diff_path],
|
|
capture_output=True
|
|
)
|
|
|
|
# Total pixels
|
|
w, h = ref_size.split('x')
|
|
total_pixels = int(w) * int(h)
|
|
|
|
return {
|
|
'total_pixels': total_pixels,
|
|
'different_pixels': ae,
|
|
'diff_percent': (ae / total_pixels * 100) if total_pixels > 0 else 0,
|
|
'rmse': rmse,
|
|
'ref_size': ref_size,
|
|
}
|
|
|
|
|
|
def generate_reference_instructions(test_dir, tests):
|
|
"""Print instructions for generating reference renders."""
|
|
missing = [t for t in tests if not os.path.exists(t['reference'])]
|
|
if not missing:
|
|
return
|
|
|
|
print(f"\n{'='*60}")
|
|
print(f"MISSING REFERENCE FILES ({len(missing)} of {len(tests)})")
|
|
print(f"{'='*60}")
|
|
print("\nTo generate reference renders from Photopea:")
|
|
print(" 1. Open https://www.photopea.com")
|
|
print(" 2. For each PSD file below:")
|
|
print(" a. File → Open → select the PSD")
|
|
print(" b. Verify the effect renders correctly")
|
|
print(" c. File → Export as → PNG")
|
|
print(f" d. Save as {{name}}_reference.png in {test_dir}/")
|
|
print()
|
|
|
|
for t in missing:
|
|
print(f" [ ] {t['name']}.psd → {t['name']}_reference.png")
|
|
|
|
|
|
def run_comparison(test_dir):
|
|
"""Run comparison for all test files."""
|
|
tests = find_test_files(test_dir)
|
|
if not tests:
|
|
print("No PSD test files found!")
|
|
return
|
|
|
|
print(f"\nFound {len(tests)} test files")
|
|
print(f"{'='*70}")
|
|
|
|
generate_reference_instructions(test_dir, tests)
|
|
|
|
results = []
|
|
has_engine_output = False
|
|
|
|
for t in tests:
|
|
print(f"\n--- {t['name']} ---")
|
|
has_ref = os.path.exists(t['reference'])
|
|
has_eng = os.path.exists(t['engine'])
|
|
|
|
print(f" PSD: {'OK' if os.path.exists(t['psd']) else 'MISSING'}")
|
|
print(f" Reference: {'OK' if has_ref else 'MISSING'}")
|
|
print(f" Engine: {'OK' if has_eng else 'MISSING'}")
|
|
|
|
if has_ref and has_eng:
|
|
has_engine_output = True
|
|
stats = pixel_compare(t['reference'], t['engine'])
|
|
if stats:
|
|
if 'error' in stats:
|
|
print(f" ERROR: {stats['error']}")
|
|
else:
|
|
print(f" Diff pixels: {stats['different_pixels']} / {stats['total_pixels']} "
|
|
f"({stats['diff_percent']:.2f}%)")
|
|
print(f" RMSE: {stats['rmse']:.6f}")
|
|
if os.path.exists(t['diff']):
|
|
print(f" Diff image: {t['diff']}")
|
|
results.append({'name': t['name'], **stats})
|
|
|
|
if results:
|
|
print(f"\n{'='*70}")
|
|
print("SUMMARY")
|
|
print(f"{'='*70}")
|
|
print(f"{'Name':<25} {'Diff %':>8} {'RMSE':>10} {'Pixels':>12}")
|
|
print(f"{'-'*25} {'-'*8} {'-'*10} {'-'*12}")
|
|
for r in results:
|
|
if 'error' not in r:
|
|
print(f"{r['name']:<25} {r['diff_percent']:>7.2f}% {r['rmse']:>10.6f} "
|
|
f"{r['different_pixels']:>7}/{r['total_pixels']}")
|
|
|
|
avg_diff = sum(r['diff_percent'] for r in results if 'error' not in r) / max(1, len(results))
|
|
print(f"\nAverage diff: {avg_diff:.2f}%")
|
|
|
|
if not has_engine_output:
|
|
print("\n--- Engine output not yet generated ---")
|
|
print("Run the Rust test_all example to generate engine composites:")
|
|
print(" cargo run --example test_all")
|
|
|
|
|
|
def main():
|
|
test_dir = TEST_DIR
|
|
if len(sys.argv) > 2 and sys.argv[1] == '--test-dir':
|
|
test_dir = sys.argv[2]
|
|
|
|
if not check_imagemagick():
|
|
return
|
|
|
|
run_comparison(test_dir)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|