feat: projeta Via Láctea fotográfica em alta resolução
This commit is contained in:
parent
ea74b4f6cd
commit
ec9814300a
3 changed files with 222 additions and 195 deletions
86
tools/build_skymap.py
Normal file
86
tools/build_skymap.py
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Gera a textura galáctica de alta resolução usada pelo wallpaper.
|
||||
|
||||
Fonte: "Gaia's sky in colour" (ESA/Gaia/DPAC, CC BY-SA 3.0 IGO),
|
||||
8000x4000 em projeção Hammer e coordenadas galácticas. A saída é plate
|
||||
carrée, adequada para amostragem direta por longitude/latitude no canvas.
|
||||
|
||||
O processamento é feito em blocos para não manter várias grades 6K inteiras
|
||||
na memória. Requer Pillow e numpy.
|
||||
"""
|
||||
|
||||
import io
|
||||
import math
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
|
||||
URL = (
|
||||
"https://upload.wikimedia.org/wikipedia/commons/e/ea/"
|
||||
"Gaia%E2%80%99s_sky_in_colour_ESA393127.png"
|
||||
)
|
||||
CACHE = Path.home() / ".cache/skeledance/gaia_allsky.png"
|
||||
OUT = Path(__file__).resolve().parent.parent / "plugin/contents/ui/milkyway.jpg"
|
||||
SIZE = (6144, 3072)
|
||||
BLOCK_ROWS = 96
|
||||
QUALITY = 92
|
||||
|
||||
|
||||
def fetch() -> bytes:
|
||||
if CACHE.exists():
|
||||
print(f"Usando cache {CACHE}")
|
||||
return CACHE.read_bytes()
|
||||
print(f"Baixando {URL} ...")
|
||||
req = urllib.request.Request(URL, headers={"User-Agent": "skeledance-build/2.0"})
|
||||
raw = urllib.request.urlopen(req, timeout=180).read()
|
||||
CACHE.parent.mkdir(parents=True, exist_ok=True)
|
||||
CACHE.write_bytes(raw)
|
||||
print(f" {len(raw) / 1e6:.1f} MB (cacheado)")
|
||||
return raw
|
||||
|
||||
|
||||
def main() -> None:
|
||||
Image.MAX_IMAGE_PIXELS = None
|
||||
src = np.asarray(Image.open(io.BytesIO(fetch())).convert("RGB"))
|
||||
sh, sw = src.shape[:2]
|
||||
tw, th = SIZE
|
||||
out = np.empty((th, tw, 3), dtype=np.uint8)
|
||||
|
||||
# Longitude cresce para a esquerda para coincidir com a imagem Gaia.
|
||||
lon = np.deg2rad(180 - (np.arange(tw, dtype=np.float64) + 0.5) * 360 / tw)
|
||||
|
||||
for y_start in range(0, th, BLOCK_ROWS):
|
||||
y_stop = min(th, y_start + BLOCK_ROWS)
|
||||
lat = np.deg2rad(
|
||||
90 - (np.arange(y_start, y_stop, dtype=np.float64) + 0.5) * 180 / th
|
||||
)
|
||||
lon_grid, lat_grid = np.meshgrid(lon, lat)
|
||||
|
||||
denom = np.sqrt(1 + np.cos(lat_grid) * np.cos(lon_grid / 2))
|
||||
hx = 2 * math.sqrt(2) * np.cos(lat_grid) * np.sin(lon_grid / 2) / denom
|
||||
hy = math.sqrt(2) * np.sin(lat_grid) / denom
|
||||
sx = (1 - hx / (2 * math.sqrt(2))) * sw / 2
|
||||
sy = (1 - hy / math.sqrt(2)) * sh / 2
|
||||
|
||||
# Amostragem bilinear preserva filamentos sem serrilhar a reprojeção.
|
||||
x0 = np.clip(np.floor(sx).astype(np.int32), 0, sw - 1)
|
||||
y0 = np.clip(np.floor(sy).astype(np.int32), 0, sh - 1)
|
||||
x1 = np.minimum(x0 + 1, sw - 1)
|
||||
y1 = np.minimum(y0 + 1, sh - 1)
|
||||
fx = (sx - x0)[..., None]
|
||||
fy = (sy - y0)[..., None]
|
||||
top = src[y0, x0] * (1 - fx) + src[y0, x1] * fx
|
||||
bottom = src[y1, x0] * (1 - fx) + src[y1, x1] * fx
|
||||
out[y_start:y_stop] = np.clip(top * (1 - fy) + bottom * fy, 0, 255)
|
||||
print(f"\rReprojetando: {y_stop * 100 // th:3d}%", end="", flush=True)
|
||||
|
||||
print()
|
||||
Image.fromarray(out).save(OUT, "JPEG", quality=QUALITY, optimize=True)
|
||||
print(f"{OUT} ({OUT.stat().st_size / 1024 / 1024:.1f} MB, {tw}x{th})")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue