Files
CM95/tools/cm95_maplibre_retina_sprite.py
2026-09-17 21:30:49 +08:00

76 lines
2.5 KiB
Python
Executable File

#!/usr/bin/env python3
from __future__ import annotations
import argparse
import json
from pathlib import Path
import shutil
import subprocess
from typing import Any
def _scale_pair_list(value: Any, factor: int) -> Any:
if not isinstance(value, list):
return value
out = []
for item in value:
if isinstance(item, list) and len(item) == 2:
out.append([item[0] * factor, item[1] * factor])
else:
out.append(item)
return out
def _retina_entry(entry: dict[str, Any], factor: int) -> dict[str, Any]:
out = dict(entry)
for key in ("x", "y", "width", "height"):
if key in out:
out[key] = int(out[key]) * factor
if isinstance(out.get("content"), list) and len(out["content"]) == 4:
out["content"] = [v * factor for v in out["content"]]
for key in ("stretchX", "stretchY"):
if key in out:
out[key] = _scale_pair_list(out[key], factor)
out["pixelRatio"] = factor
return out
def build_retina(prefix: Path, *, factor: int = 2) -> dict[str, Any]:
src_png = prefix.with_suffix(".png")
src_json = prefix.with_suffix(".json")
if not src_png.is_file() or not src_json.is_file():
raise FileNotFoundError(f"missing sprite pair for prefix {prefix}")
if shutil.which("convert") is None:
raise RuntimeError("ImageMagick convert is required")
dst_png = prefix.with_name(prefix.name + f"@{factor}x").with_suffix(".png")
dst_json = prefix.with_name(prefix.name + f"@{factor}x").with_suffix(".json")
data = json.loads(src_json.read_text(encoding="utf-8"))
retina = {name: _retina_entry(dict(entry), factor) for name, entry in data.items()}
subprocess.run(
["convert", str(src_png), "-filter", "point", "-resize", f"{factor * 100}%", str(dst_png)],
check=True,
)
dst_json.write_text(json.dumps(retina, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
return {
"prefix": str(prefix),
"entry_count": len(retina),
"png": str(dst_png),
"json": str(dst_json),
"pixel_ratio": factor,
}
def main() -> int:
ap = argparse.ArgumentParser(description="Build MapLibre @2x sprite assets from a 1x sprite pair.")
ap.add_argument("prefix", nargs="+", type=Path)
args = ap.parse_args()
result = [build_retina(prefix) for prefix in args.prefix]
print(json.dumps(result, ensure_ascii=False, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())