Files
chatSummaries/tools/icon_png.py
T
lyuksovannyyandClaude Opus 5.5 b885f2179f
Publish plugin / publish (push) Successful in 4s
[no bump] Move technical docs to docs/, add icon and PNG export tool
README now carries the icon, TL;DR, description and features; the
technical sections live in docs/. tools/icon_png.py renders
images/icon.svg to a square PNG (512x512 by default, --resolution N).
plugin.json points at the plugin's own icon.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 14:16:37 +02:00

97 lines
3.6 KiB
Python

#!/usr/bin/env python3
"""Renders images/icon.svg to a square PNG (512x512 by default).
Stdlib only: the actual rasterising is delegated to the first renderer found,
in this order: the cairosvg Python package, rsvg-convert (librsvg),
ImageMagick (magick/convert), Inkscape. Install any one of them.
Usage:
tools/icon_png.py # images/icon.svg -> images/icon.png, 512x512
tools/icon_png.py --resolution 1024 # 1024x1024
tools/icon_png.py other.svg -o out.png # explicit input/output
The output is always PNG, always <resolution>x<resolution>.
"""
from __future__ import annotations
import argparse
import shutil
import subprocess
import sys
from pathlib import Path
from typing import List, Optional
REPO_ROOT = Path(__file__).resolve().parent.parent
DEFAULT_SVG = REPO_ROOT / "images" / "icon.svg"
DEFAULT_RESOLUTION = 512
def fail(message: str) -> "None":
print(f"error: {message}", file=sys.stderr)
raise SystemExit(1)
def cli_command(svg: Path, png: Path, size: int) -> Optional[List[str]]:
"""Returns the command line for the first installed CLI renderer, if any."""
if shutil.which("rsvg-convert"):
return ["rsvg-convert", "-w", str(size), "-h", str(size), "-f", "png",
"-o", str(png), str(svg)]
magick = shutil.which("magick") or shutil.which("convert")
if magick:
# A high density rasterises the vector sharply before scaling down;
# "!" forces the exact size.
return [magick, "-background", "none", "-density", "1200", str(svg),
"-resize", f"{size}x{size}!", f"png:{png}"]
if shutil.which("inkscape"):
return ["inkscape", str(svg), "--export-type=png", f"--export-filename={png}",
"-w", str(size), "-h", str(size)]
return None
def render(svg: Path, png: Path, size: int) -> str:
"""Renders svg into png at size x size, returning the renderer's name."""
try:
import cairosvg # type: ignore[import-not-found]
except ImportError:
pass
else:
cairosvg.svg2png(url=str(svg), write_to=str(png),
output_width=size, output_height=size)
return "cairosvg"
command = cli_command(svg, png, size)
if command is None:
fail("no SVG renderer found; install one of: cairosvg (pip), "
"rsvg-convert (librsvg), ImageMagick, Inkscape")
result = subprocess.run(command, capture_output=True, text=True)
if result.returncode != 0:
fail(f"{Path(command[0]).name} failed:\n{result.stderr.strip()}")
return Path(command[0]).name
def main() -> None:
parser = argparse.ArgumentParser(description="Render an SVG icon to a square PNG.")
parser.add_argument("svg", nargs="?", type=Path, default=DEFAULT_SVG,
help="input SVG (default: images/icon.svg)")
parser.add_argument("-r", "--resolution", type=int, default=DEFAULT_RESOLUTION,
help=f"width and height in pixels (default: {DEFAULT_RESOLUTION})")
parser.add_argument("-o", "--output", type=Path,
help="output PNG (default: the input path with a .png suffix)")
args = parser.parse_args()
if args.resolution <= 0:
fail("--resolution must be a positive integer")
if not args.svg.is_file():
fail(f"{args.svg} not found")
png = args.output or args.svg.with_suffix(".png")
if png.suffix.lower() != ".png":
fail(f"output must be a .png file, got {png}")
png.parent.mkdir(parents=True, exist_ok=True)
renderer = render(args.svg, png, args.resolution)
print(f"{png} ({args.resolution}x{args.resolution}, via {renderer})")
if __name__ == "__main__":
main()