mirror of
https://github.com/block/buzz.git
synced 2026-10-27 12:40:30 +00:00
Fix rounded macOS icon on older Macs (#8018)
Older macOS versions display Buzz’s opaque icon as an oversized square. Bake rounded corners and transparent padding into the macOS `.icns`, preserving the original bee artwork and leaving other platforms unchanged. Generation uses Hermit-pinned uv, Python 3.14.3, and Pillow 12.2.0. `scripts/macos-icon.sh --check` regenerates temporarily and compares all decoded representations; macOS CI runs it before packaging. Pixel reproducibility is verified independently of Apple’s ICNS container encoding. Validated native AppKit preview, all 10 representations, fresh-cache generation, one-pixel/missing-representation drift rejection, workflow lint, and pre-push checks. Full local `just ci` is running. Packaged Dock checks on older/current macOS remain outstanding. --------- Signed-off-by: kenny lopez <klopez4212@gmail.com>
This commit is contained in:
94
scripts/generate-macos-icon.py
Normal file
94
scripts/generate-macos-icon.py
Normal file
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build the legacy macOS icon without relying on system corner masking.
|
||||
|
||||
Run: scripts/macos-icon.sh [--check]
|
||||
|
||||
The wrapper pins uv, Python and Pillow. Verification compares the complete set
|
||||
of decoded RGBA representations, not PNG encoding or ICNS container bytes:
|
||||
Apple's system iconutil may encode the same pixels differently across macOS.
|
||||
The shared artwork and other platforms' icons are intentionally untouched.
|
||||
"""
|
||||
|
||||
# /// script
|
||||
# requires-python = "==3.14.3"
|
||||
# dependencies = ["Pillow==12.2.0"]
|
||||
# ///
|
||||
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
ICONS = ROOT / "desktop/src-tauri/icons"
|
||||
|
||||
|
||||
def generate(output):
|
||||
# An 824px tile on a 1024px canvas leaves the macOS optical inset.
|
||||
# Work at 4x resolution to antialias the rounded edge at every icon size.
|
||||
scale = 4
|
||||
canvas = Image.new("RGBA", (1024 * scale, 1024 * scale))
|
||||
with Image.open(ICONS / "buzz-source.png") as source:
|
||||
tile = source.convert("RGBA").resize(
|
||||
(824 * scale, 824 * scale), Image.Resampling.LANCZOS
|
||||
)
|
||||
mask = Image.new("L", tile.size)
|
||||
ImageDraw.Draw(mask).rounded_rectangle(
|
||||
(0, 0, tile.width - 1, tile.height - 1), radius=185 * scale, fill=255
|
||||
)
|
||||
canvas.paste(tile, (100 * scale, 100 * scale), mask)
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix="buzz-macos-icon-") as temporary:
|
||||
iconset = Path(temporary) / "Buzz.iconset"
|
||||
iconset.mkdir()
|
||||
for size in (16, 32, 128, 256, 512):
|
||||
for density in (1, 2):
|
||||
suffix = "@2x" if density == 2 else ""
|
||||
canvas.resize(
|
||||
(size * density, size * density), Image.Resampling.LANCZOS
|
||||
).save(iconset / f"icon_{size}x{size}{suffix}.png")
|
||||
subprocess.run(
|
||||
["/usr/bin/iconutil", "-c", "icns", str(iconset), "-o", str(output)],
|
||||
check=True,
|
||||
)
|
||||
|
||||
|
||||
def verify(candidate, reference):
|
||||
"""Reject missing/extra representations and any decoded pixel drift."""
|
||||
with tempfile.TemporaryDirectory(prefix="buzz-icon-check-") as temporary:
|
||||
sets = []
|
||||
for name, icon in (("candidate", candidate), ("reference", reference)):
|
||||
iconset = Path(temporary) / f"{name}.iconset"
|
||||
subprocess.run(
|
||||
["/usr/bin/iconutil", "-c", "iconset", str(icon), "-o", str(iconset)],
|
||||
check=True,
|
||||
)
|
||||
sets.append({p.name: p for p in iconset.glob("*.png")})
|
||||
if sets[0].keys() != sets[1].keys():
|
||||
raise ValueError("macOS icon representation set drifted")
|
||||
for name in sorted(sets[0]):
|
||||
with Image.open(sets[0][name]) as a, Image.open(sets[1][name]) as b:
|
||||
if a.size != b.size or a.convert("RGBA").tobytes() != b.convert("RGBA").tobytes():
|
||||
raise ValueError(f"macOS icon pixel drift: {name}")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--check", action="store_true", help="verify without modifying the icon")
|
||||
parser.add_argument("--output", type=Path, default=ICONS / "icon.icns")
|
||||
args = parser.parse_args()
|
||||
if args.check:
|
||||
with tempfile.TemporaryDirectory(prefix="buzz-icon-regenerate-") as temporary:
|
||||
generated = Path(temporary) / "icon.icns"
|
||||
generate(generated)
|
||||
verify(args.output, generated)
|
||||
print("macOS icon: all decoded representations match")
|
||||
else:
|
||||
generate(args.output)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
7
scripts/macos-icon.sh
Executable file
7
scripts/macos-icon.sh
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
# Hermit pins uv; inline script metadata pins Python and Pillow exactly.
|
||||
# uv script environments are isolated from the caller's Python packages.
|
||||
exec "$ROOT/bin/uv" run --managed-python --python 3.14.3 \
|
||||
--script "$ROOT/scripts/generate-macos-icon.py" "$@"
|
||||
Reference in New Issue
Block a user