Skip to content

Commit d27f39b

Browse files
author
TechStack Global
committed
fix(ui): re-engineer 16x16 and 32x32 favicons to use pixel-perfect boolean grids instead of downscaled anti-aliasing
1 parent a21c3c8 commit d27f39b

File tree

7 files changed

+62
-48
lines changed

7 files changed

+62
-48
lines changed
-892 Bytes
Loading
-2.62 KB
Loading
-895 Bytes
Loading

assets/favicon/favicon-16x16.png

-397 Bytes
Loading

assets/favicon/favicon-32x32.png

-993 Bytes
Loading

assets/favicon/favicon.ico

-3.5 KB
Binary file not shown.

generate_favicon.py

Lines changed: 62 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -2,75 +2,89 @@
22
from PIL import Image, ImageDraw, ImageFont
33

44
def create_favicon():
5-
size = 1024
6-
bg_color = '#020617' # Site primary background (bg-darker)
7-
text_color = '#ffffff'
5+
target_dir = os.path.join("assets", "favicon")
6+
os.makedirs(target_dir, exist_ok=True)
7+
8+
bg_color = (2, 6, 23) # #020617
9+
text_color = (255, 255, 255) # #ffffff
10+
11+
# ---------------------------------------------------------
12+
# 1) Generate the 16x16 PIXEL-PERFECT grid to avoid ALL blur
13+
# ---------------------------------------------------------
14+
pixel_map = [
15+
"0000000000000000",
16+
"0000000000000000",
17+
"0000000000000000",
18+
"0000000000000000",
19+
"0000000000000000",
20+
"0011101110011100",
21+
"0001001000100000",
22+
"0001001110101100",
23+
"0001000010100100",
24+
"0001001110011100",
25+
"0000000000000000",
26+
"0000000000000000",
27+
"0000000000000000",
28+
"0000000000000000",
29+
"0000000000000000",
30+
"0000000000000000"
31+
]
32+
33+
img16 = Image.new('RGB', (16, 16), color=bg_color)
34+
for y, row in enumerate(pixel_map):
35+
for x, char in enumerate(row):
36+
if char == '1':
37+
img16.putpixel((x, y), text_color)
38+
39+
# Save the perfect 16x16
40+
img16.save(os.path.join(target_dir, "favicon-16x16.png"))
841

42+
# Scale up mathematically exactly for 32x32 so it remains perfectly crisp
43+
img32 = img16.resize((32, 32), Image.Resampling.NEAREST)
44+
img32.save(os.path.join(target_dir, "favicon-32x32.png"))
45+
46+
# ---------------------------------------------------------
47+
# 2) Generate the 512x512 High-Res Icons using standard vectors
48+
# ---------------------------------------------------------
49+
size = 1024
950
img = Image.new('RGB', (size, size), color=bg_color)
1051
draw = ImageDraw.Draw(img)
1152

12-
# Try Verdana Bold for maximum clarity at tiny sizes
1353
try:
14-
font = ImageFont.truetype("C:\\Windows\\Fonts\\verdanab.ttf", int(size * 0.45))
15-
except IOError:
1654
font = ImageFont.truetype("C:\\Windows\\Fonts\\arialbd.ttf", int(size * 0.45))
55+
except IOError:
56+
font = ImageFont.load_default()
1757

18-
text = "TSG"
19-
20-
# Get accurate bounding box
58+
text = "T S G"
2159
left, top, right, bottom = draw.textbbox((0, 0), text, font=font)
2260
text_w = right - left
2361
text_h = bottom - top
2462

25-
# Center mathematically exactly on the text pixels
63+
if text_w > size * 0.9:
64+
scale = (size * 0.9) / text_w
65+
font = ImageFont.truetype("C:\\Windows\\Fonts\\arialbd.ttf", int(size * 0.45 * scale))
66+
left, top, right, bottom = draw.textbbox((0, 0), text, font=font)
67+
text_w = right - left
68+
text_h = bottom - top
69+
2670
x = (size - text_w) / 2 - left
2771
y = (size - text_h) / 2 - top
28-
2972
draw.text((x, y), text, fill=text_color, font=font)
30-
31-
target_dir = os.path.join("assets", "favicon")
32-
os.makedirs(target_dir, exist_ok=True)
3373

3474
resample = Image.Resampling.LANCZOS
3575

3676
img.resize((512, 512), resample).save(os.path.join(target_dir, "android-chrome-512x512.png"))
3777
img.resize((192, 192), resample).save(os.path.join(target_dir, "android-chrome-192x192.png"))
3878
img.resize((180, 180), resample).save(os.path.join(target_dir, "apple-touch-icon.png"))
39-
img.resize((32, 32), resample).save(os.path.join(target_dir, "favicon-32x32.png"))
4079

41-
# Sharp downscale for 16x16
42-
img16 = img.resize((128, 128), resample).resize((16, 16), resample)
43-
img16.save(os.path.join(target_dir, "favicon-16x16.png"))
44-
45-
# generate ico
80+
# Multi-size ICO: 16 (pixel art), 32 (pixel art), 48 (lanczos)
81+
img48 = img.resize((48, 48), resample)
4682
icon_sizes = [(16, 16), (32, 32), (48, 48)]
47-
# Use the 48x48 from lanczos for the base of ico
48-
icon_img = img.resize((48, 48), resample)
49-
img.save(os.path.join(target_dir, "favicon.ico"), format='ICO', sizes=icon_sizes)
50-
51-
# Re-write the manifest to match the new background color
52-
manifest = """{
53-
"name": "TechStack Global",
54-
"short_name": "TSG",
55-
"icons": [
56-
{
57-
"src": "/assets/favicon/android-chrome-192x192.png",
58-
"sizes": "192x192",
59-
"type": "image/png"
60-
},
61-
{
62-
"src": "/assets/favicon/android-chrome-512x512.png",
63-
"sizes": "512x512",
64-
"type": "image/png"
65-
}
66-
],
67-
"theme_color": "#020617",
68-
"background_color": "#020617",
69-
"display": "standalone"
70-
}"""
71-
with open(os.path.join(target_dir, "site.webmanifest"), "w") as f:
72-
f.write(manifest)
73-
83+
84+
# We can create a multi-sized ICO by passing multiple images to the save method,
85+
# or let PIL downscale from img. Since we want our custom 16 and 32, we should use append_images.
86+
img16.save(os.path.join(target_dir, "favicon.ico"), format='ICO', sizes=icon_sizes, append_images=[img32, img48])
87+
7488
if __name__ == "__main__":
7589
create_favicon()
76-
print("Favicons re-generated perfectly!")
90+
print("Favicons generated with True Pixel-Perfect precision!")

0 commit comments

Comments
 (0)