1
0
mirror of https://github.com/emilk/egui.git synced 2026-08-30 05:10:03 -04:00

Support loading images with weird urls and improve error message (#5431)

* Closes #5341
* [x] I have followed the instructions in the PR template
This commit is contained in:
lucasmerlin
2024-12-05 07:33:02 +01:00
committed by GitHub
parent f687b27efc
commit 291b83b7be
4 changed files with 70 additions and 29 deletions

View File

@@ -28,7 +28,7 @@ pub struct RetainedImage {
}
impl RetainedImage {
pub fn from_color_image(debug_name: impl Into<String>, image: ColorImage) -> Self {
pub fn from_color_image(debug_name: impl Into<String>, image: egui::ColorImage) -> Self {
Self {
debug_name: debug_name.into(),
size: image.size,
@@ -54,7 +54,7 @@ impl RetainedImage {
) -> Result<Self, String> {
Ok(Self::from_color_image(
debug_name,
load_image_bytes(image_bytes)?,
load_image_bytes(image_bytes).map_err(|err| err.to_string())?,
))
}
@@ -154,7 +154,7 @@ impl RetainedImage {
self.texture
.lock()
.get_or_insert_with(|| {
let image: &mut ColorImage = &mut self.image.lock();
let image: &mut egui::ColorImage = &mut self.image.lock();
let image = std::mem::take(image);
ctx.load_texture(&self.debug_name, image, self.options)
})
@@ -190,8 +190,6 @@ impl RetainedImage {
// ----------------------------------------------------------------------------
use egui::ColorImage;
/// Load a (non-svg) image.
///
/// Requires the "image" feature. You must also opt-in to the image formats you need
@@ -200,9 +198,19 @@ use egui::ColorImage;
/// # Errors
/// On invalid image or unsupported image format.
#[cfg(feature = "image")]
pub fn load_image_bytes(image_bytes: &[u8]) -> Result<egui::ColorImage, String> {
pub fn load_image_bytes(image_bytes: &[u8]) -> Result<egui::ColorImage, egui::load::LoadError> {
crate::profile_function!();
let image = image::load_from_memory(image_bytes).map_err(|err| err.to_string())?;
let image = image::load_from_memory(image_bytes).map_err(|err| match err {
image::ImageError::Unsupported(err) => match err.kind() {
image::error::UnsupportedErrorKind::Format(format) => {
egui::load::LoadError::FormatNotSupported {
detected_format: Some(format.to_string()),
}
}
_ => egui::load::LoadError::Loading(err.to_string()),
},
err => egui::load::LoadError::Loading(err.to_string()),
})?;
let size = [image.width() as _, image.height() as _];
let image_buffer = image.to_rgba8();
let pixels = image_buffer.as_flat_samples();

View File

@@ -7,7 +7,7 @@ use egui::{
use image::ImageFormat;
use std::{mem::size_of, path::Path, sync::Arc};
type Entry = Result<Arc<ColorImage>, String>;
type Entry = Result<Arc<ColorImage>, LoadError>;
#[derive(Default)]
pub struct ImageCrateLoader {
@@ -31,9 +31,14 @@ fn is_supported_uri(uri: &str) -> bool {
.any(|format_ext| ext == *format_ext)
}
fn is_unsupported_mime(mime: &str) -> bool {
fn is_supported_mime(mime: &str) -> bool {
// This is the default mime type for binary files, so this might actually be a valid image,
// let's relay on image's format guessing
if mime == "application/octet-stream" {
return true;
}
// Uses only the enabled image crate features
!ImageFormat::all()
ImageFormat::all()
.filter(ImageFormat::reading_enabled)
.map(|fmt| fmt.to_mime_type())
.any(|format_mime| mime == format_mime)
@@ -46,12 +51,12 @@ impl ImageLoader for ImageCrateLoader {
fn load(&self, ctx: &egui::Context, uri: &str, _: SizeHint) -> ImageLoadResult {
// three stages of guessing if we support loading the image:
// 1. URI extension
// 1. URI extension (only done for files)
// 2. Mime from `BytesPoll::Ready`
// 3. image::guess_format
// 3. image::guess_format (used internally by image::load_from_memory)
// (1)
if !is_supported_uri(uri) {
if uri.starts_with("file://") && !is_supported_uri(uri) {
return Err(LoadError::NotSupported);
}
@@ -59,26 +64,26 @@ impl ImageLoader for ImageCrateLoader {
if let Some(entry) = cache.get(uri).cloned() {
match entry {
Ok(image) => Ok(ImagePoll::Ready { image }),
Err(err) => Err(LoadError::Loading(err)),
Err(err) => Err(err),
}
} else {
match ctx.try_load_bytes(uri) {
Ok(BytesPoll::Ready { bytes, mime, .. }) => {
// (2 and 3)
if mime.as_deref().is_some_and(is_unsupported_mime)
|| image::guess_format(&bytes).is_err()
{
return Err(LoadError::NotSupported);
// (2)
if let Some(mime) = mime {
if !is_supported_mime(&mime) {
return Err(LoadError::FormatNotSupported {
detected_format: Some(mime),
});
}
}
// (3)
log::trace!("started loading {uri:?}");
let result = crate::image::load_image_bytes(&bytes).map(Arc::new);
log::trace!("finished loading {uri:?}");
cache.insert(uri.into(), result.clone());
match result {
Ok(image) => Ok(ImagePoll::Ready { image }),
Err(err) => Err(LoadError::Loading(err)),
}
result.map(|image| ImagePoll::Ready { image })
}
Ok(BytesPoll::Pending { size }) => Ok(ImagePoll::Pending { size }),
Err(err) => Err(err),
@@ -100,7 +105,7 @@ impl ImageLoader for ImageCrateLoader {
.values()
.map(|result| match result {
Ok(image) => image.pixels.len() * size_of::<egui::Color32>(),
Err(err) => err.len(),
Err(err) => err.byte_size(),
})
.sum()
}