#!/usr/bin/env python3 """Generate OpenMapClassic's edge-feather masks. A feather is a mask over a map sheet's whole rect: opaque in the middle, falling to nothing at the border, so the sheet blends into what is around it instead of ending in a straight line. Upstream's own NOTICE calls these "generated from noise" rather than cut from art -- they are parametric, so this reproduces them rather than approximating an artist. 1.12 needs them where Classic Era does not. Era's `C_Map` zone textures carry a transparent surround; the 1.12 sheets in this client's archives are **fully opaque** (measured: alpha 255 everywhere) and paint their own sea, lettering and a torn parchment border right to the edge. So a zone drawn without a mask covers its neighbours with a rectangle -- which is what `OpenMapClassic/Map/Stitch.lua`'s own comment predicts, and what the Gilneas sheet does over Silverpine. Writes 256x256 uncompressed 32-bit TGA, matching the shipped `MapZoneDilate` masks: RGB black, alpha the coverage. """ import sys from pathlib import Path import numpy as np from PIL import Image SIZE = 256 # Margin as a fraction of the sheet, per variant. The parchment border on a 1002-px sheet runs # ~35 px (3.5%); a feather has to clear that and then blend, so the plain one takes 9%. VARIANTS = {"MapFeather": 0.09, "MapFeatherThin": 0.045, "MapFeatherWide": 0.16} def ramp(n: int, margin: float) -> np.ndarray: """1 in the middle, 0 at both ends, smoothstep across `margin` of the span.""" x = (np.arange(n) + 0.5) / n d = np.minimum(x, 1.0 - x) / margin # distance from the nearer edge, in margins t = np.clip(d, 0.0, 1.0) return t * t * (3.0 - 2.0 * t) def feather(margin: float) -> Image.Image: a = np.outer(ramp(SIZE, margin), ramp(SIZE, margin)) rgba = np.zeros((SIZE, SIZE, 4), dtype=np.uint8) rgba[:, :, 3] = np.round(a * 255.0).astype(np.uint8) return Image.fromarray(rgba, "RGBA") def main() -> int: if len(sys.argv) != 2: print("usage: make-map-feather.py ", file=sys.stderr) return 2 out = Path(sys.argv[1]) if not out.is_dir(): print(f"not a directory: {out}", file=sys.stderr) return 2 for name, margin in VARIANTS.items(): path = out / f"{name}.tga" feather(margin).save(path) print(f"wrote {path} ({margin:.0%} margin)") return 0 if __name__ == "__main__": raise SystemExit(main())