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:
2 files changed
+20
-5
No files matched your search
@@ -13,7 +13,19 @@ pub fn extract_palette(image_path: &str) -> Result<ThemePalette, String> {
|
|||||||
return Err(format!("Image not found: {}", image_path));
|
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.
|
// Downsample to 100x100 for fast processing.
|
||||||
let thumb = img.resize_exact(100, 100, image::imageops::FilterType::Lanczos3);
|
let thumb = img.resize_exact(100, 100, image::imageops::FilterType::Lanczos3);
|
||||||
|
|||||||
@@ -75,10 +75,13 @@ pub fn apply_startup(window: &AppWindow) {
|
|||||||
/// Regenerate the auto theme off-thread and push the result on the event loop.
|
/// Regenerate the auto theme off-thread and push the result on the event loop.
|
||||||
/// Toggles `auto-theme-generating` around the work and toasts on failure.
|
/// Toggles `auto-theme-generating` around the work and toasts on failure.
|
||||||
pub fn regenerate(weak: slint::Weak<AppWindow>, handle: tokio::runtime::Handle) {
|
pub fn regenerate(weak: slint::Weak<AppWindow>, handle: tokio::runtime::Handle) {
|
||||||
if let Some(w) = weak.upgrade() {
|
// Through the event loop, NOT a direct upgrade: regenerate() is also
|
||||||
w.global::<AppearanceState>()
|
// called from tokio contexts (select_image), where a plain upgrade()
|
||||||
.set_auto_theme_generating(true);
|
// returns None and the generating indicator would silently be skipped.
|
||||||
}
|
let weak_flag = weak.clone();
|
||||||
|
let _ = weak_flag.upgrade_in_event_loop(move |w| {
|
||||||
|
w.global::<AppearanceState>().set_auto_theme_generating(true);
|
||||||
|
});
|
||||||
handle.spawn(async move {
|
handle.spawn(async move {
|
||||||
let prefs = crate::ui_prefs::load();
|
let prefs = crate::ui_prefs::load();
|
||||||
let source = source_from_prefs(&prefs);
|
let source = source_from_prefs(&prefs);
|
||||||
|
|||||||
Reference in new issue
Block a user