Files

204 lines
6.4 KiB
Python
Raw Permalink Normal View History

2026-07-09 02:59:53 +03:00
#!/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()