76 lines
2.5 KiB
Python
Executable File
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())
|