Skip to content

Commit 1b48a3c

Browse files
melevittflclaude
andcommitted
Fix icon: move to brand/images/, use Pillow for proper Windows ICO format
Regenerate the .ico with Pillow (BMP-in-ICO) instead of sips+struct (PNG-in-ICO) so PyInstaller correctly applies it to the Windows .exe. Move the file from brand/ to brand/images/ and update app.spec and scripts/make_icon.py to match. CI now installs Pillow and regenerates the icon before calling PyInstaller. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 56c4d7c commit 1b48a3c

5 files changed

Lines changed: 18 additions & 60 deletions

File tree

.github/workflows/build-windows.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,11 @@ jobs:
2121
with:
2222
python-version: '3.13'
2323

24-
- name: Install PyInstaller
25-
run: pip install pyinstaller
24+
- name: Install dependencies
25+
run: pip install pyinstaller pillow
26+
27+
- name: Generate icon
28+
run: python scripts/make_icon.py
2629

2730
- name: Build binary
2831
run: pyinstaller app.spec

app.spec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,5 @@ exe = EXE(
3737
target_arch=None,
3838
codesign_identity=None,
3939
entitlements_file=None,
40-
icon='brand/dassiedrop.ico',
40+
icon='brand/images/dassiedrop.ico',
4141
)

brand/dassiedrop.ico

-71.2 KB
Binary file not shown.

brand/images/dassiedrop.ico

70.5 KB
Binary file not shown.

scripts/make_icon.py

Lines changed: 12 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,38 @@
11
#!/usr/bin/env python3
2-
"""Generate brand/dassiedrop.ico from the brand PNG icon.
2+
"""Generate brand/images/dassiedrop.ico from the brand PNG icon using Pillow.
33
4-
Requires macOS (uses sips for lossless PNG resizing).
5-
Run once and commit the output; CI uses the committed file directly.
4+
Install Pillow first: pip install pillow
65
76
Usage:
87
python scripts/make_icon.py
98
"""
10-
import io
11-
import struct
12-
import subprocess
139
import sys
14-
import tempfile
1510
from pathlib import Path
1611

1712
REPO_ROOT = Path(__file__).resolve().parent.parent
1813
SOURCE = REPO_ROOT / "brand" / "images" / "dassiedrop-icon.png.png"
19-
OUTPUT = REPO_ROOT / "brand" / "dassiedrop.ico"
14+
OUTPUT = REPO_ROOT / "brand" / "images" / "dassiedrop.ico"
2015
SIZES = [16, 32, 48, 64, 128, 256]
2116

2217

23-
def resize_png(source: Path, size: int) -> bytes:
24-
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
25-
tmp_path = Path(tmp.name)
26-
try:
27-
subprocess.run(
28-
[
29-
"sips",
30-
"--resampleHeightWidth", str(size), str(size),
31-
str(source),
32-
"--out", str(tmp_path),
33-
],
34-
check=True,
35-
capture_output=True,
36-
)
37-
return tmp_path.read_bytes()
38-
finally:
39-
tmp_path.unlink(missing_ok=True)
40-
41-
42-
def make_ico(images: list[tuple[int, bytes]]) -> bytes:
43-
count = len(images)
44-
data_offset = 6 + 16 * count
45-
46-
buf = io.BytesIO()
47-
buf.write(struct.pack("<HHH", 0, 1, count)) # ICONDIR header
48-
49-
offset = data_offset
50-
for size, data in images:
51-
w = 0 if size == 256 else size # 0 encodes 256 in the ICO spec
52-
h = 0 if size == 256 else size
53-
buf.write(struct.pack("<BBBBHHII", w, h, 0, 0, 1, 32, len(data), offset))
54-
offset += len(data)
55-
56-
for _, data in images:
57-
buf.write(data)
58-
59-
return buf.getvalue()
60-
61-
6218
def main() -> None:
63-
if sys.platform != "darwin":
64-
print("This script requires macOS (uses sips). Commit the generated .ico instead.")
19+
try:
20+
from PIL import Image
21+
except ImportError:
22+
print("Pillow is required: pip install pillow")
6523
sys.exit(1)
6624

6725
if not SOURCE.exists():
6826
print(f"Source not found: {SOURCE}")
6927
sys.exit(1)
7028

7129
print(f"Source : {SOURCE.relative_to(REPO_ROOT)}")
72-
images = []
73-
for size in SIZES:
74-
data = resize_png(SOURCE, size)
75-
images.append((size, data))
76-
print(f" {size:>3}x{size:<3} {len(data):>7,} bytes")
30+
img = Image.open(SOURCE).convert("RGBA")
7731

78-
ico = make_ico(images)
79-
OUTPUT.write_bytes(ico)
80-
print(f"Output : {OUTPUT.relative_to(REPO_ROOT)} ({len(ico):,} bytes total)")
32+
size_tuples = [(s, s) for s in SIZES]
33+
img.save(str(OUTPUT), format="ICO", sizes=size_tuples)
34+
print(f"Output : {OUTPUT.relative_to(REPO_ROOT)} ({OUTPUT.stat().st_size:,} bytes)")
35+
print(f"Sizes : {', '.join(f'{s}x{s}' for s in SIZES)}")
8136

8237

8338
if __name__ == "__main__":

0 commit comments

Comments
 (0)