irongit

Git hosting and a container registry in one Rust binary (axum + Astro)

Logo: stacked bars with the green keyed out; despill after resizing so edges stay clean

huncholanehuncholaneauthored
parent 520a1d6commit d2a0133c3869b474c9d8805da928e90f23ed0552Browse files

10 files changed, +19 -5

+1-1backend/src/web/ui.rs
@@ -4,7 +4,7 @@ use chrono::{DateTime, Utc};
44 use maud::{Markup, PreEscaped, html};
55 use sha2::{Digest, Sha256};
66
7-/// The vault-door mark (frontend/public/logo-128.png, built by
7+/// The stacked-bars mark (frontend/public/logo-128.png, built by
88 /// scripts/gen-brand.py), sharp up to about 40 px on 3x screens.
99 pub fn logo(size: u32) -> Markup {
1010 html! {
+0-0brand/logo-source.jpg

Binary file changed.

+0-0frontend/public/apple-touch-icon.png

Binary file changed.

+0-0frontend/public/favicon-32.png

Binary file changed.

+0-0frontend/public/favicon.ico

Binary file changed.

+0-0frontend/public/icon-192.png

Binary file changed.

+0-0frontend/public/logo-128.png

Binary file changed.

+0-0frontend/public/logo.png

Binary file changed.

+0-0scripts/__pycache__/gen-brand.cpython-313.pyc

Binary file changed.

+18-4scripts/gen-brand.py
@@ -1,5 +1,5 @@
11 #!/usr/bin/env python3
2-"""Builds the irongit icons from brand/logo-source.jpg (the vault door on a
2+"""Builds the irongit icons from brand/logo-source.jpg (the stacked bars on a
33 flat green background).
44
55 The background is keyed out by how green each pixel is; edge pixels are
@@ -47,7 +47,8 @@ def key_out_green(path: Path) -> Image.Image:
4747 a = alpha[..., None]
4848 fg = np.where(a > 0.02, (rgb - (1.0 - a) * bg) / np.maximum(a, 1e-3), 0.0)
4949 fg = np.clip(fg, 0, 255)
50- # The artwork is neutral; remove any green spill left in the edge colours.
50+ # The artwork is neutral greys and gold (red above green); remove any green
51+ # spill left in the edge colours.
5152 spill_cap = np.maximum(fg[..., 0], fg[..., 2]) + 3
5253 fg[..., 1] = np.minimum(fg[..., 1], spill_cap)
5354
@@ -66,8 +67,19 @@ def key_out_green(path: Path) -> Image.Image:
6667 return square
6768
6869
70+def despill(image: Image.Image) -> Image.Image:
71+ """Caps green at the larger of red and blue. Neutral greys and gold
72+ (red above green) are untouched; only spill and resampling tint goes."""
73+ px = np.asarray(image).copy()
74+ cap = np.maximum(px[..., 0], px[..., 2]).astype(np.int16) + 3
75+ px[..., 1] = np.minimum(px[..., 1], np.clip(cap, 0, 255)).astype(np.uint8)
76+ return Image.fromarray(px, image.mode)
77+
78+
6979 def resized(image: Image.Image, size: int) -> Image.Image:
70- return image.resize((size, size), Image.Resampling.LANCZOS)
80+ # Lanczos overshoots on the faint edge pixels beside the gold rims and
81+ # tints them olive, so the spill cap runs again after every resize.
82+ return despill(image.resize((size, size), Image.Resampling.LANCZOS))
7183
7284
7385 def main() -> int:
@@ -83,7 +95,9 @@ def main() -> int:
8395 touch.paste(inner, (14, 14), inner)
8496 touch.convert("RGB").save(OUT / "apple-touch-icon.png", optimize=True)
8597
86- resized(master, 256).save(OUT / "favicon.ico", sizes=[(16, 16), (32, 32), (48, 48)])
98+ # Each favicon size is resized (and despilled) here rather than by the ICO writer.
99+ icons = [resized(master, n) for n in (48, 32, 16)]
100+ icons[0].save(OUT / "favicon.ico", sizes=[(48, 48), (32, 32), (16, 16)], append_images=icons[1:])
87101 for name in ["logo.png", "logo-128.png", "icon-192.png", "favicon-32.png", "apple-touch-icon.png", "favicon.ico"]:
88102 print(f" {name:22} {(OUT / name).stat().st_size:>7} bytes")
89103 return 0