diff --git a/moon_composite.py b/moon_composite.py index 152e3ba..357a71f 100755 --- a/moon_composite.py +++ b/moon_composite.py @@ -110,36 +110,40 @@ def _atmospheric_sky_background( moon_cy: float, moon_diam_px: float, ) -> Image.Image: - """Real atmospheric halo on a deep night-sky backdrop. + """Smooth atmospheric glow on a deep night-sky backdrop. - Inside ~1.8× the moon radius the real sky is preserved (gamma-darkened to - collapse diffuse cloud texture while the bright atmospheric glow survives). - Beyond ~4.5× the radius it fades smoothly to a near-black night colour, so - the result looks like a genuine dark-sky photograph rather than an - over-exposed, heavily upscaled camera frame. + Samples the real halo colour from a ring just outside the moon disk, then + builds a smooth radial gradient from that colour at the disk edge to deep + night-sky beyond ~2.5× the radius. No sky-cam pixels are used directly, + so there is no cloud texture or foil artefact regardless of how bright or + hazy the original sky was. """ arr = np.asarray(sky_img).astype(np.float32) h, w = arr.shape[:2] - - # Deep night-sky colour — very dark desaturated blue - night = np.array([4, 6, 14], dtype=np.float32) - dark_bg = np.broadcast_to(night, (h, w, 3)).copy() + r = moon_diam_px / 2.0 yy, xx = np.ogrid[0:h, 0:w] dist = np.sqrt((xx - moon_cx) ** 2 + (yy - moon_cy) ** 2) - r = moon_diam_px / 2.0 - inner_r = 1.3 * r # real-sky halo fully preserved inside here - outer_r = 2.2 * r # fully dark outside here — keeps frame corners black + # Sample mean colour of the real halo in a ring from 1.15× to 1.8× radius. + ring = (dist >= 1.15 * r) & (dist < 1.8 * r) + if ring.any(): + halo_rgb = arr[ring].mean(axis=0) # real atmospheric colour + else: + halo_rgb = np.array([80, 85, 95], dtype=np.float32) + # Scale to a tasteful glow level (original is often overexposed) + halo_rgb = np.clip(halo_rgb * 0.35, 0, 180).astype(np.float32) - alpha = np.clip((outer_r - dist) / (outer_r - inner_r), 0.0, 1.0)[..., None] + # Deep night-sky colour — very dark desaturated blue + night = np.array([4, 6, 14], dtype=np.float32) - # Gamma + scale: compresses diffuse cloud texture toward black while the - # bright atmospheric halo (already near-white) survives nearly intact. - real_darkened = np.power(np.clip(arr / 255.0, 0, 1), 1.6) * 0.65 * 255.0 + # Radial alpha: 1.0 at the moon disk edge (dist_norm=1), 0 at 3× radius. + dist_norm = dist / r + glow_alpha = np.clip(1.0 - (dist_norm - 1.0) / 2.0, 0.0, 1.0) ** 2 + glow_alpha = glow_alpha[..., None] - blended = alpha * real_darkened + (1.0 - alpha) * dark_bg - return Image.fromarray(np.clip(blended, 0, 255).astype(np.uint8)) + bg_arr = glow_alpha * halo_rgb + (1.0 - glow_alpha) * night + return Image.fromarray(np.clip(bg_arr, 0, 255).astype(np.uint8)) def _disk_mask(size: int, feather_px: int = 6) -> Image.Image: