"""Rebuild a neutral 2K albedo from the raw MV Hebat Allah scan texture. The underwater scan has a dead red channel (mean 17/255) and a strong blue cast, so a white balance only produces magenta noise. This script keeps the luminance of the two live channels, stretches it, and tints it with a rust / algae palette driven by the green-to-blue ratio. The game's fog adds the water colour at runtime. python scripts/assets/hebat_allah_prepare_albedo.py Writes albedo_2k_balanced.png and albedo_1k_balanced.png into . Requires Pillow and numpy. Licence: see docs/ASSET_LICENSES.md. """ import sys from pathlib import Path import numpy as np from PIL import Image def balance(source: Path, out_dir: Path) -> None: image = Image.open(source).convert("RGB").resize((2048, 2048), Image.LANCZOS) rgb = np.asarray(image).astype(np.float32) / 255.0 luminance = 0.55 * rgb[..., 1] + 0.45 * rgb[..., 2] low, high = np.percentile(luminance, 1), np.percentile(luminance, 99) luminance = np.clip((luminance - low) / (high - low), 0.0, 1.0) luminance = luminance ** 0.9 * 0.75 + 0.08 algae_ratio = np.clip((rgb[..., 1] - rgb[..., 2] * 0.68) * 3.0, 0.0, 1.0) rust = np.array([0.62, 0.52, 0.44]) algae = np.array([0.50, 0.58, 0.46]) tint = rust[None, None, :] * (1.0 - algae_ratio[..., None]) + algae[None, None, :] * algae_ratio[..., None] out = np.clip(luminance[..., None] * tint / 0.55, 0.0, 1.0) result = Image.fromarray((out * 255).astype(np.uint8)) out_dir.mkdir(parents=True, exist_ok=True) result.save(out_dir / "albedo_2k_balanced.png") result.resize((1024, 1024), Image.LANCZOS).save(out_dir / "albedo_1k_balanced.png") if __name__ == "__main__": if len(sys.argv) != 3: sys.exit(__doc__) balance(Path(sys.argv[1]), Path(sys.argv[2]))