Skip to content

Commit 38b5a6d

Browse files
fix(ci): AVIF on Ubuntu 24.04 - no extra apt pkg; extend AVIF to non-alpha PNGs
Agent-Logs-Url: https://github.com/winnerspiros/osu/sessions/c4dcc58e-7290-465e-ad33-535af4ca79cf Co-authored-by: winnerspiros <1675249+winnerspiros@users.noreply.github.com>
1 parent 5b2ae75 commit 38b5a6d

3 files changed

Lines changed: 18 additions & 13 deletions

File tree

.github/resource-optimizer/config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"png_webp_lossy_method": 6,
2020
"jpeg_webp_quality": 88,
2121
"jpeg_webp_method": 6,
22-
"jpeg_avif_enabled": false,
22+
"jpeg_avif_enabled": true,
2323
"jpeg_avif_crf": 30,
2424
"jpeg_avif_preset": 4,
2525
"audio_codec": "libopus",

osu.Game/Resources/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ That means you can keep call sites unchanged and still serve a compressed local
2424

2525
**AVIF vs WebP**: AVIF is tried first because it offers better compression than WebP at equal quality (attractive for large backgrounds/splash art). WebP is the safe fallback because it is supported everywhere. The framework's `TextureLoaderStore` applies the same AVIF-first order with ImageSharp capability checking, so AVIF will be silently skipped on any platform where ImageSharp cannot decode it.
2626

27-
**⚠️ Do NOT generate AVIF for PNG files with alpha channels (e.g. font atlases, UI sprites)**: `libsvtav1` encodes only yuv420p and silently strips the alpha channel, producing a tiny (~350 byte) but completely blank/solid output. `libaom-av1` would preserve alpha via yuva420p but is extremely slow and AVIF alpha support is inconsistent on Android. Use WebP for all PNG assets — WebP lossless perfectly preserves alpha. AVIF is only safe for JPEG-sourced images (no alpha channel) and even then savings over WebP are marginal.
27+
**⚠️ Do NOT generate AVIF for PNG files with alpha channels (e.g. font atlases, UI sprites)**: `libsvtav1` encodes only yuv420p and silently strips the alpha channel, producing a tiny (~350 byte) but completely blank/solid output. Use WebP for all PNG assets that carry transparency — WebP lossless perfectly preserves alpha.
28+
29+
For PNG files **without** an alpha channel, AVIF is safe and is tried alongside WebP; the smaller of the two (that passes the SSIM threshold) is kept. The optimizer uses `ffprobe` to detect whether a PNG has an alpha channel before attempting AVIF.
2830

2931
Example:
3032

scripts/optimize_resource_overrides.py

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,12 @@ def convert_image(source: Path, config: dict, relative_path: str) -> tuple[Path,
9393
"""
9494
Convert a source image to the best compressed format available.
9595
96-
For PNG: tries lossless WebP and lossy WebP; picks the smallest that meets the SSIM
97-
threshold. AVIF is explicitly excluded for PNGs because libsvtav1 only supports
98-
yuv420p (no alpha channel), which would silently strip transparency.
96+
For PNG with alpha: tries lossless WebP and lossy WebP only (AVIF is skipped because
97+
libsvtav1 encodes yuv420p only and silently strips the alpha channel).
9998
100-
For JPEG/JPG: tries lossy WebP and, when ``jpeg_avif_enabled`` is true in config,
101-
lossy AVIF via libsvtav1 (yuv420p — safe for JPEG sources which have no alpha).
99+
For PNG without alpha and for JPEG/JPG: tries lossy WebP and, when
100+
``jpeg_avif_enabled`` is true in config, also lossy AVIF via libsvtav1
101+
(yuv420p — safe when there is no alpha channel).
102102
Picks the smallest candidate that meets the SSIM threshold.
103103
104104
Returns ``(temp_output_path, strategy)`` where ``temp_output_path.suffix`` is the
@@ -142,14 +142,17 @@ def convert_image(source: Path, config: dict, relative_path: str) -> tuple[Path,
142142
)
143143
temp_candidates.append((p_webp, f"{ext.lstrip('.')}-lossy-webp-q{lossy_quality}", False, ".webp"))
144144

145-
# ── AVIF via libsvtav1 (JPEG/JPG only, opt-in via jpeg_avif_enabled) ─────
146-
# PNG files must NOT use AVIF: libsvtav1 encodes yuv420p only and silently
147-
# strips alpha channels, producing a tiny but completely transparent output.
148-
# JPEG sources have no alpha, so yuv420p is safe.
149-
if ext in {".jpg", ".jpeg"} and bool(config.get("jpeg_avif_enabled", False)):
145+
# ── AVIF via libsvtav1 (JPEG/JPG + opaque PNG, opt-in via jpeg_avif_enabled) ──
146+
# PNG files with alpha must NOT use AVIF: libsvtav1 encodes yuv420p only and
147+
# silently strips alpha channels, producing a tiny but completely transparent
148+
# output. PNG files without alpha and all JPEG sources are safe to encode as
149+
# AVIF because yuv420p has no alpha plane.
150+
can_use_avif = ext in {".jpg", ".jpeg"} or (ext == ".png" and not has_alpha)
151+
if can_use_avif and bool(config.get("jpeg_avif_enabled", False)):
150152
avif_crf = int(config.get("jpeg_avif_crf", 30))
151153
avif_preset = int(config.get("jpeg_avif_preset", 4))
152154
p_avif = source.parent / f".{source.stem}.lossy.avif.tmp"
155+
src_label = "jpg" if ext in {".jpg", ".jpeg"} else "png"
153156
try:
154157
run_ffmpeg(
155158
[
@@ -162,7 +165,7 @@ def convert_image(source: Path, config: dict, relative_path: str) -> tuple[Path,
162165
str(p_avif),
163166
]
164167
)
165-
temp_candidates.append((p_avif, f"jpg-avif-svtav1-crf{avif_crf}-p{avif_preset}", False, ".avif"))
168+
temp_candidates.append((p_avif, f"{src_label}-avif-svtav1-crf{avif_crf}-p{avif_preset}", False, ".avif"))
166169
except subprocess.CalledProcessError:
167170
# Log a warning so the operator knows AVIF was requested but unavailable.
168171
print(f"::warning::AVIF requested for {relative_path} but libsvtav1 encode failed; falling back to WebP.")

0 commit comments

Comments
 (0)