icons: ship the leather mark, with a simplified version for tab size

Christian's leather patch art is the app icon at 180px and up. It cannot be
the favicon: leather grain and stitching turn to mud below 48px, and the
pulse is black on brown, which is not enough contrast in a tab bar.

So the set is two-tier. Sizes 180 and above come from the leather art;
16, 32, 48 and the .ico come from a flat source whose spike geometry was
measured off the original (left tail at 52% height, peak at 42% width,
trough at 54%, right tail at 84%). Same colours sampled from the leather
body, so the small mark reads as the same object rather than a different
logo.

make_icons.py gained --small-from for this: one command builds both tiers.

Signed-off-by: Christian Sass <csass@binaryranch.com>
This commit is contained in:
Christian Sass 2026-10-21 22:24:01 -05:00
parent 2af4a877ec
commit e5fc0a1d35
12 changed files with 24 additions and 4 deletions

BIN
icons/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

BIN
icons/icon-1024.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

BIN
icons/icon-16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 702 B

BIN
icons/icon-180.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

BIN
icons/icon-256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

BIN
icons/icon-32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
icons/icon-48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
icons/icon-512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 650 KiB

6
icons/source-flat.svg Normal file
View file

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024" viewBox="0 0 1024 1024">
<rect width="1024" height="1024" rx="224" fill="#583828"/>
<path d="M164 533 H352 L433 200 L540 840 L655 533 H859"
fill="none" stroke="#f0ede8" stroke-width="74"
stroke-linecap="square" stroke-linejoin="miter"/>
</svg>

After

Width:  |  Height:  |  Size: 330 B

BIN
icons/source-leather.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

6
icons/source-small.svg Normal file
View file

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024" viewBox="0 0 1024 1024">
<rect width="1024" height="1024" rx="224" fill="#583828"/>
<path d="M180 520 H420 L512 250 L604 790 L700 520 H844"
fill="none" stroke="#f0ede8" stroke-width="132"
stroke-linecap="square" stroke-linejoin="miter"/>
</svg>

After

Width:  |  Height:  |  Size: 331 B

View file

@ -61,21 +61,29 @@ def main():
ap = argparse.ArgumentParser()
ap.add_argument("source", help="square-ish .svg or .png/.jpg mark")
ap.add_argument("--out-dir", default="icons")
ap.add_argument("--small-from", default="",
help="optional simpler source used for 16/32/48 and the .ico, "
"because fine detail dies at tab size")
ap.add_argument("--small-max", type=int, default=48)
args = ap.parse_args()
if not os.path.exists(args.source):
sys.exit(f"no such file: {args.source}")
master = load_square(args.source)
small = load_square(args.small_from) if args.small_from else None
os.makedirs(args.out_dir, exist_ok=True)
for size in SIZES:
src = small if (small and size <= args.small_max) else master
out = os.path.join(args.out_dir, f"icon-{size}.png")
master.resize((size, size), RESAMPLE).save(out)
print(f" {out}")
src.resize((size, size), RESAMPLE).save(out)
print(f" {out}{' (simplified)' if src is small else ''}")
ico_path = os.path.join(args.out_dir, "favicon.ico")
master.resize((256, 256), RESAMPLE).save(ico_path, sizes=[(s, s) for s in ICO_SIZES])
print(f" {ico_path} ({', '.join(str(s) for s in ICO_SIZES)})")
(small or master).resize((256, 256), RESAMPLE).save(
ico_path, sizes=[(s, s) for s in ICO_SIZES])
print(f" {ico_path} ({', '.join(str(s) for s in ICO_SIZES)})"
f"{' (simplified)' if small else ''}")
print(f"\n{len(SIZES) + 1} files in {args.out_dir}/")
print("upload icon-512.png as the org avatar on both forges; the rest ship with the app")