fix(theme): bound auto-theme image decode, spinner via event loop

Review follow-ups: extract_palette decoded the full image before the
100x100 downsample with no limits — a decompression-bomb PNG expands
to tens of GB, and this box hard-freezes on OOM; decode is now bounded
at 12k x 12k / 512 MB (any real wallpaper fits). The generating
indicator is now set through upgrade_in_event_loop so the select_image
path (which runs on a tokio thread, where a plain upgrade() returns
None) shows it too. Startup generation stays synchronous by design:
with the decode bounded it costs milliseconds and the first paint
comes up in the generated palette instead of flashing the default.

cc
This commit is contained in:
vicrodh committed 2026-07-05 22:44:50 -06:00
1 parent 1bbc98cc8e
commit e70d434773
2 files changed
+20 -5

No files matched your search

+13 -1
View File
@@ -13,7 +13,19 @@ pub fn extract_palette(image_path: &str) -> Result<ThemePalette, String> {
return Err(format!("Image not found: {}", image_path));
}
let img = image::open(path).map_err(|e| format!("Failed to open image: {}", e))?;
// Bounded decode: a decompression-bomb PNG (65k x 65k) would otherwise
// expand to tens of GB before the 100x100 downsample. 12k x 12k / 512 MB
// covers any real wallpaper while keeping the worst case survivable.
let mut reader = image::ImageReader::open(path)
.map_err(|e| format!("Failed to open image: {}", e))?
.with_guessed_format()
.map_err(|e| format!("Failed to read image: {}", e))?;
let mut limits = image::Limits::default();
limits.max_image_width = Some(12_000);
limits.max_image_height = Some(12_000);
limits.max_alloc = Some(512 * 1024 * 1024);
reader.limits(limits);
let img = reader.decode().map_err(|e| format!("Failed to decode image: {}", e))?;
// Downsample to 100x100 for fast processing.
let thumb = img.resize_exact(100, 100, image::imageops::FilterType::Lanczos3);