feat(decorations): 530 new avatar decorations, thumbnail-first picker
CI / Build & Quality Checks (push) Successful in 3m34s
CI / Docker image build & smoke test (push) Skipped
CI / Secret scan (gitleaks) (push) Successful in 11s
CI / Trigger Desktop Build (push) Successful in 15s
CI / Playwright smoke (e2e) (push) Successful in 10m56s
CI / Build & Quality Checks (push) Successful in 3m34s
CI / Docker image build & smoke test (push) Skipped
CI / Secret scan (gitleaks) (push) Successful in 11s
CI / Trigger Desktop Build (push) Successful in 15s
CI / Playwright smoke (e2e) (push) Successful in 10m56s
Adds 530 decorations in 19 new categories (631 total). Decorations that contain Discord branding (the Clyde-visor helmets) are excluded. The decorations are ~1 MB animated PNGs, so a picker that rendered every one would pull ~590 MB while scrolling. The picker now: - shows static 144px WebP thumbnails (~9.5 KB each, `thumbs/` on the CDN) and loads the animated file only on hover, focus or selection, with a fallback to the full file if a thumbnail is missing; - mounts one category at a time behind tabs, plus a name search across all categories. scripts/makeDecorationThumbs.py builds the thumbnails from the busiest frame of each animation (many start on an empty frame). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
4323babfb9
commit
78b1a2ff6f
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build static picker thumbnails for avatar decorations.
|
||||
|
||||
The decorations are animated APNGs (~1 MB each). The settings picker shows
|
||||
static WebP thumbnails instead and only loads the animated file on hover,
|
||||
focus or selection, so browsing the catalog costs a few MB, not hundreds.
|
||||
|
||||
Usage: python3 scripts/makeDecorationThumbs.py <dir-of-slug.png> <out-dir>
|
||||
Then upload <out-dir>/*.webp to `${DECORATION_CDN}/thumbs/`.
|
||||
A missing thumbnail is harmless: the picker falls back to the full PNG.
|
||||
|
||||
Requires Pillow (pip install pillow).
|
||||
"""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from PIL import Image, ImageSequence
|
||||
|
||||
SIZE = 144 # 2x the 72px picker cell
|
||||
|
||||
|
||||
def best_frame(im: Image.Image) -> Image.Image:
|
||||
# Many animations start (or loop through) an empty frame, so pick the frame
|
||||
# with the most visible pixels rather than frame 0.
|
||||
best, best_score = None, -1
|
||||
frames = list(ImageSequence.Iterator(im))
|
||||
step = max(1, len(frames) // 24)
|
||||
for frame in frames[::step]:
|
||||
rgba = frame.convert('RGBA')
|
||||
score = sum(rgba.getchannel('A').histogram()[33:])
|
||||
if score > best_score:
|
||||
best, best_score = rgba, score
|
||||
return best
|
||||
|
||||
|
||||
def main(src: str, out: str) -> None:
|
||||
out_dir = Path(out)
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
for png in sorted(Path(src).glob('*.png')):
|
||||
with Image.open(png) as im:
|
||||
thumb = best_frame(im).resize((SIZE, SIZE), Image.LANCZOS)
|
||||
thumb.save(out_dir / f'{png.stem}.webp', 'WEBP', quality=82, method=6)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) != 3:
|
||||
sys.exit(__doc__)
|
||||
main(sys.argv[1], sys.argv[2])
|
||||
Reference in New Issue
Block a user