From 9bb36b0ac2b64ac94d4cb7bef5d8e3e28e87a6c5 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 17 Aug 2026 21:41:09 -0700 Subject: [PATCH] Match image file extensions case-insensitively (#8430) `image.PNG` and `cat.SVG` were not recognized as images. Adds `egui::load::has_extension(uri, extension)`, which ignores ASCII case and any `#fragment`, and uses it for the `.svg`, `.gif`, `.webp` and `.png` checks. Note: gif/webp URIs like `a#b.gif` no longer match, since the fragment is now excluded. * [x] I have followed the instructions in the PR template Co-authored-by: Claude Opus 5 (1M context) --- crates/eframe/src/native/glow_integration.rs | 2 +- crates/egui/src/load.rs | 24 ++++++++++++++++++++ crates/egui/src/load/texture_loader.rs | 2 +- crates/egui/src/widgets/image.rs | 4 ++-- crates/egui_extras/src/loaders/svg_loader.rs | 2 +- 5 files changed, 29 insertions(+), 5 deletions(-) diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index 1b9469cfd..cca1d22c6 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -1720,7 +1720,7 @@ fn save_screenshot_and_exit( screen_size_in_pixels: [u32; 2], ) { assert!( - path.ends_with(".png"), + egui::load::has_extension(path, "png"), "Expected EFRAME_SCREENSHOT_TO to end with '.png', got {path:?}" ); let screenshot = painter.read_screen_rgba(screen_size_in_pixels); diff --git a/crates/egui/src/load.rs b/crates/egui/src/load.rs index f3acbf6af..df390dc64 100644 --- a/crates/egui/src/load.rs +++ b/crates/egui/src/load.rs @@ -306,6 +306,19 @@ macro_rules! generate_loader_id { } pub use crate::generate_loader_id; +/// Does the given URI end with the given file extension? +/// +/// The comparison ignores ASCII case and any `#fragment` at the end of the URI, +/// so `has_extension("cat.GIF#frame=2", "gif")` is `true`. +/// +/// This is useful when implementing an [`ImageLoader`]. +pub fn has_extension(uri: &str, extension: &str) -> bool { + let path = uri.split('#').next().unwrap_or(uri); + std::path::Path::new(path) + .extension() + .is_some_and(|found| found.eq_ignore_ascii_case(extension)) +} + pub type BytesLoadResult = Result; /// Represents a loader capable of loading raw unstructured bytes from somewhere, @@ -639,3 +652,14 @@ impl Loaders { } } } + +#[test] +fn test_has_extension() { + assert!(has_extension("cat.svg", "svg")); + assert!(has_extension("cat.SVG", "svg")); + assert!(has_extension("http://example.com/cat.gif#frame=2", "gif")); + assert!(!has_extension("cat.svg.png", "svg")); + assert!(!has_extension("svg", "svg")); + assert!(!has_extension("cat.jpeg", "jpg")); + assert!(!has_extension("cat.svg?v=1", "svg")); +} diff --git a/crates/egui/src/load/texture_loader.rs b/crates/egui/src/load/texture_loader.rs index 2ab4d8a57..eb9b4503b 100644 --- a/crates/egui/src/load/texture_loader.rs +++ b/crates/egui/src/load/texture_loader.rs @@ -150,5 +150,5 @@ impl TextureLoader for DefaultTextureLoader { } fn is_svg(uri: &str) -> bool { - uri.ends_with(".svg") + super::has_extension(uri, "svg") } diff --git a/crates/egui/src/widgets/image.rs b/crates/egui/src/widgets/image.rs index 0618e8661..9bce8c4d3 100644 --- a/crates/egui/src/widgets/image.rs +++ b/crates/egui/src/widgets/image.rs @@ -934,7 +934,7 @@ fn animated_image_frame_index(ctx: &Context, uri: &str) -> usize { /// Checks if uri is a gif file fn is_gif_uri(uri: &str) -> bool { - uri.ends_with(".gif") || uri.contains(".gif#") + crate::load::has_extension(uri, "gif") } /// Checks if bytes are gifs @@ -944,7 +944,7 @@ pub fn has_gif_magic_header(bytes: &[u8]) -> bool { /// Checks if uri is a webp file fn is_webp_uri(uri: &str) -> bool { - uri.ends_with(".webp") || uri.contains(".webp#") + crate::load::has_extension(uri, "webp") } /// Checks if bytes are webp diff --git a/crates/egui_extras/src/loaders/svg_loader.rs b/crates/egui_extras/src/loaders/svg_loader.rs index 91063f6b4..53c879056 100644 --- a/crates/egui_extras/src/loaders/svg_loader.rs +++ b/crates/egui_extras/src/loaders/svg_loader.rs @@ -29,7 +29,7 @@ impl SvgLoader { } fn is_supported(uri: &str) -> bool { - uri.ends_with(".svg") + egui::load::has_extension(uri, "svg") } impl Default for SvgLoader {