diff options
Diffstat (limited to 'tools/sky/build-milkyway.py')
| -rw-r--r-- | tools/sky/build-milkyway.py | 41 |
1 files changed, 41 insertions, 0 deletions
diff --git a/tools/sky/build-milkyway.py b/tools/sky/build-milkyway.py new file mode 100644 index 0000000..c9a63bd --- /dev/null +++ b/tools/sky/build-milkyway.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +# Turns NASA SVS's "Deep Star Maps 2020" Milky Way background (the star map +# without its Hipparcos and Tycho stars, which the sky draws itself) into the +# sRGB JPEG the sky component maps onto the celestial sphere. +# +# curl -O https://svs.gsfc.nasa.gov/vis/a000000/a004800/a004851/milkyway_2020_4k.exr +# python3 tools/sky/build-milkyway.py milkyway_2020_4k.exr public/assets/sky/milkyway-4k.jpg +# +# Needs vips (to read the OpenEXR), numpy and Pillow. The map is plate carrée +# in J2000 right ascension and declination, centred on 0h with r.a. increasing +# to the left; this keeps that layout. Values stay linear in brightness up to +# WHITE (only the heart of the Sagittarius star cloud goes past it), and are +# stored with the sRGB curve so the faint parts keep their detail in 8 bits. +import os +import subprocess +import sys +import tempfile + +import numpy as np +from PIL import Image + +WHITE = 0.6 + + +def main(src, dst): + width, height = (int(subprocess.check_output(['vipsheader', '-f', f, src], stderr=subprocess.DEVNULL)) for f in ('width', 'height')) + with tempfile.TemporaryDirectory() as tmp: + raw = os.path.join(tmp, 'map.raw') + subprocess.run(['vips', 'rawsave', src, raw], check=True, stderr=subprocess.DEVNULL) + rgb = np.fromfile(raw, dtype=np.float32).reshape(height, width, -1)[..., :3] + x = np.clip(rgb / WHITE, 0.0, 1.0) + srgb = np.where(x <= 0.0031308, 12.92 * x, 1.055 * np.power(x, 1 / 2.4) - 0.055) + # A little noise before rounding keeps the dark gradients from banding. + noise = np.random.default_rng(1).uniform(-0.5, 0.5, srgb.shape) + out = np.clip(np.round(srgb * 255 + noise), 0, 255).astype(np.uint8) + Image.fromarray(out, 'RGB').save(dst, quality=88, optimize=True, progressive=True) + print(f'{width}x{height} -> {dst} ({os.path.getsize(dst) // 1024} KB)') + + +if __name__ == '__main__': + main(sys.argv[1], sys.argv[2]) |