0.2.3: 重绘系列通用安装图标并记录功能审核决定(#413、#77/#408、#444、#407)
This commit is contained in:
1 parent
ba5b998a72
commit
9042125430
21 files changed
+249
-13
No files matched your search
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Export the original app.svg offline: python3 tools/export-app-icons.py.
|
||||
|
||||
Requires Python 3 + Pillow (also used by local-extract). This deliberately renders
|
||||
only this source's flat rect/polygon vocabulary; unsupported SVG fails loudly.
|
||||
No fonts, game art, browser, network or platform image service is involved.
|
||||
"""
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SOURCE = ROOT / "public/icons/app.svg"
|
||||
MASK_SCALE = 0.875
|
||||
|
||||
|
||||
def render(size, maskable=False):
|
||||
svg = ET.parse(SOURCE).getroot()
|
||||
if svg.attrib != {"width": "512", "height": "512", "viewBox": "0 0 512 512"}:
|
||||
raise ValueError("Expected the 512-square SVG master")
|
||||
# Eight samples per output pixel, then area averaging; no ringing beyond the mark.
|
||||
scale = size * 8 / 512
|
||||
image = Image.new("RGB", (size * 8, size * 8))
|
||||
draw = ImageDraw.Draw(image)
|
||||
|
||||
def paint(element, inset=1):
|
||||
tag = element.tag.rsplit("}", 1)[-1]
|
||||
attrs = element.attrib
|
||||
if tag in ("title", "desc"):
|
||||
if attrs or len(element):
|
||||
raise ValueError("Unsupported metadata")
|
||||
return
|
||||
if tag == "g" and attrs == {"id": "mark"}:
|
||||
for child in element:
|
||||
paint(child, MASK_SCALE if maskable else 1)
|
||||
return
|
||||
if tag == "rect" and set(attrs) <= {"x", "y", "width", "height", "fill"}:
|
||||
x, y = float(attrs.get("x", 0)), float(attrs.get("y", 0))
|
||||
w, h = float(attrs["width"]), float(attrs["height"])
|
||||
points = [(x, y), (x + w, y), (x + w, y + h), (x, y + h)]
|
||||
elif tag == "polygon" and set(attrs) == {"fill", "points"}:
|
||||
points = [tuple(map(float, p.split(","))) for p in attrs["points"].split()]
|
||||
else:
|
||||
raise ValueError(f"Unsupported SVG element/attributes: {tag} {attrs}")
|
||||
# Pillow addresses pixel centres; shift the vector edges by half a sample.
|
||||
points = [((256 + (x - 256) * inset) * scale - 0.5,
|
||||
(256 + (y - 256) * inset) * scale - 0.5) for x, y in points]
|
||||
draw.polygon(points, fill=attrs["fill"])
|
||||
|
||||
for element in svg:
|
||||
paint(element)
|
||||
return image.resize((size, size), Image.Resampling.BOX)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--out", type=Path, default=ROOT / "public/icons")
|
||||
args = parser.parse_args()
|
||||
args.out.mkdir(parents=True, exist_ok=True)
|
||||
for size in (192, 512):
|
||||
for maskable in (False, True):
|
||||
name = f"app-{'maskable-' if maskable else ''}{size}.png"
|
||||
render(size, maskable).save(args.out / name)
|
||||
for size in (16, 32, 48):
|
||||
render(size).save(args.out / f"favicon-{size}.png")
|
||||
render(180).save(args.out / "apple-touch-icon.png")
|
||||
# BMP-backed frames also work in older ICO readers; PNG favicons ship separately.
|
||||
render(48).save(args.out / "favicon.ico", sizes=[(16, 16), (32, 32), (48, 48)], bitmap_format="bmp")
|
||||
print(f"Exported 9 icon files to {args.out}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user