Files
u-desktop/scripts/build_tray_icon.py

99 lines
3.1 KiB
Python

from __future__ import annotations
import argparse
from pathlib import Path
from PIL import Image, ImageChops
ICON_SIZES = (16, 20, 24, 32, 40, 48, 64, 128, 256)
def remove_green_background(image: Image.Image) -> Image.Image:
rgba = image.convert("RGBA")
pixels = rgba.load()
width, height = rgba.size
for y in range(height):
for x in range(width):
r, g, b, a = pixels[x, y]
green_score = g - max(r, b)
is_key = g > 90 and green_score > 35
is_fringe = g > 32 and g > r * 1.35 and g > b * 1.20
if is_key or is_fringe:
edge = max(0, min(255, (green_score - 18) * 5))
alpha = max(0, 255 - edge)
if alpha < 56:
pixels[x, y] = (0, 0, 0, 0)
else:
despilled_g = min(g, max(r, b))
pixels[x, y] = (r, despilled_g, b, min(a, alpha))
return rgba
def crop_to_alpha(image: Image.Image, padding_ratio: float) -> Image.Image:
alpha = image.getchannel("A")
bbox = alpha.getbbox()
if not bbox:
raise ValueError("source image has no visible pixels after background removal")
cropped = image.crop(bbox)
side = max(cropped.size)
padding = round(side * padding_ratio)
canvas_side = side + padding * 2
canvas = Image.new("RGBA", (canvas_side, canvas_side), (0, 0, 0, 0))
canvas.alpha_composite(cropped, ((canvas_side - cropped.width) // 2, (canvas_side - cropped.height) // 2))
return canvas
def trim_transparent_padding(image: Image.Image) -> Image.Image:
empty = Image.new("RGBA", image.size, (0, 0, 0, 0))
bbox = ImageChops.difference(image, empty).getbbox()
if bbox is None:
return image
return image.crop(bbox)
def build_icons(source: Path, out_dir: Path, padding_ratio: float) -> None:
out_dir.mkdir(parents=True, exist_ok=True)
source_image = Image.open(source)
transparent = remove_green_background(source_image)
master = crop_to_alpha(transparent, padding_ratio)
master = trim_transparent_padding(master)
master_path = out_dir / "tray-icon.png"
master.save(master_path)
resized_images: list[Image.Image] = []
for size in ICON_SIZES:
resized = master.resize((size, size), Image.Resampling.LANCZOS)
resized.save(out_dir / f"tray-icon-{size}.png")
resized_images.append(resized)
ico_path = out_dir / "tray.ico"
resized_images[-1].save(
ico_path,
format="ICO",
sizes=[(size, size) for size in ICON_SIZES],
append_images=resized_images[:-1],
)
print(f"wrote {master_path}")
print(f"wrote {ico_path}")
def main() -> None:
parser = argparse.ArgumentParser(description="Build u-desktop tray icon assets from a generated source image.")
parser.add_argument("--source", type=Path, required=True, help="Generated source PNG on a green chroma-key background.")
parser.add_argument("--out-dir", type=Path, default=Path("assets/icons"), help="Directory for PNG sizes and tray.ico.")
parser.add_argument("--padding", type=float, default=0.04, help="Transparent padding ratio around the cropped icon.")
args = parser.parse_args()
build_icons(args.source, args.out_dir, args.padding)
if __name__ == "__main__":
main()