From 6b3ffac85c25cfdf0313448122884e35d8f9b4a4 Mon Sep 17 00:00:00 2001 From: robtfm <50659922+robtfm@users.noreply.github.com> Date: Tue, 20 Feb 2024 13:19:20 +0000 Subject: [PATCH 1/6] guess image formats --- crates/bevy_render/src/texture/image.rs | 22 +++++++++++++++++++ .../bevy_render/src/texture/image_loader.rs | 15 +++++++++++++ 2 files changed, 37 insertions(+) diff --git a/crates/bevy_render/src/texture/image.rs b/crates/bevy_render/src/texture/image.rs index aef212100c21b..44509946ff836 100644 --- a/crates/bevy_render/src/texture/image.rs +++ b/crates/bevy_render/src/texture/image.rs @@ -99,6 +99,26 @@ impl ImageFormat { ImageFormat::Basis | ImageFormat::Ktx2 => return None, }) } + + pub fn from_image_crate_format(format: image::ImageFormat) -> Option { + Some(match format { + image::ImageFormat::Avif => ImageFormat::Avif, + image::ImageFormat::Bmp => ImageFormat::Bmp, + image::ImageFormat::Dds => ImageFormat::Dds, + image::ImageFormat::Farbfeld => ImageFormat::Farbfeld, + image::ImageFormat::Gif => ImageFormat::Gif, + image::ImageFormat::OpenExr => ImageFormat::OpenExr, + image::ImageFormat::Hdr => ImageFormat::Hdr, + image::ImageFormat::Ico => ImageFormat::Ico, + image::ImageFormat::Jpeg => ImageFormat::Jpeg, + image::ImageFormat::Png => ImageFormat::Png, + image::ImageFormat::Pnm => ImageFormat::Pnm, + image::ImageFormat::Tga => ImageFormat::Tga, + image::ImageFormat::Tiff => ImageFormat::Tiff, + image::ImageFormat::WebP => ImageFormat::WebP, + _ => return None, + }) + } } #[derive(Asset, Reflect, Debug, Clone)] @@ -734,6 +754,8 @@ pub enum TextureError { InvalidImageMimeType(String), #[error("invalid image extension: {0}")] InvalidImageExtension(String), + #[error("invalid image content type: {0:?}")] + InvalidImageContentType(image::ImageFormat), #[error("failed to load an image: {0}")] ImageError(#[from] image::ImageError), #[error("unsupported texture format: {0}")] diff --git a/crates/bevy_render/src/texture/image_loader.rs b/crates/bevy_render/src/texture/image_loader.rs index 44a4fdb9251cf..23344d7c65e0f 100644 --- a/crates/bevy_render/src/texture/image_loader.rs +++ b/crates/bevy_render/src/texture/image_loader.rs @@ -49,6 +49,7 @@ pub(crate) const IMG_FILE_EXTENSIONS: &[&str] = &[ #[derive(Serialize, Deserialize, Default, Debug)] pub enum ImageFormatSetting { #[default] + FromContent, FromExtension, Format(ImageFormat), } @@ -98,6 +99,20 @@ impl AssetLoader for ImageLoader { let mut bytes = Vec::new(); reader.read_to_end(&mut bytes).await?; let image_type = match settings.format { + ImageFormatSetting::FromContent => { + let image_crate_format = + image::guess_format(&bytes).map_err(|err| FileTextureError { + error: TextureError::ImageError(err), + path: format!("{}", load_context.path().display()), + })?; + + let format = ImageFormat::from_image_crate_format(image_crate_format) + .ok_or_else(|| FileTextureError { + error: TextureError::InvalidImageContentType(image_crate_format), + path: format!("{}", load_context.path().display()), + })?; + ImageType::Format(format) + } ImageFormatSetting::FromExtension => ImageType::Extension(ext), ImageFormatSetting::Format(format) => ImageType::Format(format), }; From 2ecf8127586353ca4c5d6bb660f7979687400971 Mon Sep 17 00:00:00 2001 From: robtfm <50659922+robtfm@users.noreply.github.com> Date: Wed, 21 Feb 2024 09:56:27 +0000 Subject: [PATCH 2/6] Update crates/bevy_render/src/texture/image.rs Co-authored-by: Alice Cecile --- crates/bevy_render/src/texture/image.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/bevy_render/src/texture/image.rs b/crates/bevy_render/src/texture/image.rs index 44509946ff836..e7a0bd35055c8 100644 --- a/crates/bevy_render/src/texture/image.rs +++ b/crates/bevy_render/src/texture/image.rs @@ -100,6 +100,9 @@ impl ImageFormat { }) } + /// Attempts to convert from [`image::ImageFormat`]. + /// + /// Unsupported formats will become [`None`]. pub fn from_image_crate_format(format: image::ImageFormat) -> Option { Some(match format { image::ImageFormat::Avif => ImageFormat::Avif, From 87ddce9732eb857ed0c4e0986b3a37c1c7d4db9a Mon Sep 17 00:00:00 2001 From: robtfm <50659922+robtfm@users.noreply.github.com> Date: Wed, 21 Feb 2024 10:03:50 +0000 Subject: [PATCH 3/6] docs --- crates/bevy_render/src/texture/image.rs | 9 +++++++++ crates/bevy_render/src/texture/image_loader.rs | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/crates/bevy_render/src/texture/image.rs b/crates/bevy_render/src/texture/image.rs index e7a0bd35055c8..ddc5747a89b1a 100644 --- a/crates/bevy_render/src/texture/image.rs +++ b/crates/bevy_render/src/texture/image.rs @@ -45,6 +45,9 @@ pub enum ImageFormat { } impl ImageFormat { + /// Returns the image format for the given mime type. + /// + /// Unsupported formats will become [`None`]. pub fn from_mime_type(mime_type: &str) -> Option { Some(match mime_type.to_ascii_lowercase().as_str() { "image/bmp" | "image/x-bmp" => ImageFormat::Bmp, @@ -58,6 +61,9 @@ impl ImageFormat { }) } + /// Returns the image format for the given extension. + /// + /// Unsupported formats will become [`None`]. pub fn from_extension(extension: &str) -> Option { Some(match extension.to_ascii_lowercase().as_str() { "avif" => ImageFormat::Avif, @@ -80,6 +86,9 @@ impl ImageFormat { }) } + /// Attempts to convert to [`image::ImageFormat`]. + /// + /// Unsupported formats will become [`None`]. pub fn as_image_crate_format(&self) -> Option { Some(match self { ImageFormat::Avif => image::ImageFormat::Avif, diff --git a/crates/bevy_render/src/texture/image_loader.rs b/crates/bevy_render/src/texture/image_loader.rs index 23344d7c65e0f..40ae8df822924 100644 --- a/crates/bevy_render/src/texture/image_loader.rs +++ b/crates/bevy_render/src/texture/image_loader.rs @@ -46,11 +46,17 @@ pub(crate) const IMG_FILE_EXTENSIONS: &[&str] = &[ "ppm", ]; +/// The method for determining the [`ImageFormat`] of the loaded image. +/// +/// By default we attempt to determine the format automatically from the header block of the binary data. #[derive(Serialize, Deserialize, Default, Debug)] pub enum ImageFormatSetting { + /// Determine the image format by inspecting the header block of the image binary data. #[default] FromContent, + /// Determine the image format from the filename extension. FromExtension, + /// Specify an explicit format for the image. Format(ImageFormat), } From 06be77a26c305f2b768204593347c4d1f65ed08a Mon Sep 17 00:00:00 2001 From: robtfm <50659922+robtfm@users.noreply.github.com> Date: Wed, 21 Feb 2024 12:37:27 +0000 Subject: [PATCH 4/6] format->to_string --- crates/bevy_render/src/texture/image_loader.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bevy_render/src/texture/image_loader.rs b/crates/bevy_render/src/texture/image_loader.rs index 40ae8df822924..63295ca87f450 100644 --- a/crates/bevy_render/src/texture/image_loader.rs +++ b/crates/bevy_render/src/texture/image_loader.rs @@ -109,13 +109,13 @@ impl AssetLoader for ImageLoader { let image_crate_format = image::guess_format(&bytes).map_err(|err| FileTextureError { error: TextureError::ImageError(err), - path: format!("{}", load_context.path().display()), + path: load_context.path().display().to_string(), })?; let format = ImageFormat::from_image_crate_format(image_crate_format) .ok_or_else(|| FileTextureError { error: TextureError::InvalidImageContentType(image_crate_format), - path: format!("{}", load_context.path().display()), + path: load_context.path().display().to_string(), })?; ImageType::Format(format) } @@ -134,7 +134,7 @@ impl AssetLoader for ImageLoader { ) .map_err(|err| FileTextureError { error: err, - path: format!("{}", load_context.path().display()), + path: load_context.path().display().to_string(), })?) }) } From 3805f6c27b4e54b20a0ffe1331a8543d5b83b5d7 Mon Sep 17 00:00:00 2001 From: robtfm <50659922+robtfm@users.noreply.github.com> Date: Wed, 21 Feb 2024 15:38:14 +0000 Subject: [PATCH 5/6] add file type/content validation --- crates/bevy_render/src/texture/image.rs | 2 +- crates/bevy_render/src/texture/image_loader.rs | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/crates/bevy_render/src/texture/image.rs b/crates/bevy_render/src/texture/image.rs index ddc5747a89b1a..698c6159a714d 100644 --- a/crates/bevy_render/src/texture/image.rs +++ b/crates/bevy_render/src/texture/image.rs @@ -24,7 +24,7 @@ use wgpu::{Extent3d, TextureDimension, TextureFormat, TextureViewDescriptor}; pub const TEXTURE_ASSET_INDEX: u64 = 0; pub const SAMPLER_ASSET_INDEX: u64 = 1; -#[derive(Debug, Serialize, Deserialize, Copy, Clone)] +#[derive(Debug, Serialize, Deserialize, Copy, Clone, PartialEq, Eq)] pub enum ImageFormat { Avif, Basis, diff --git a/crates/bevy_render/src/texture/image_loader.rs b/crates/bevy_render/src/texture/image_loader.rs index 63295ca87f450..5e22467fdfb2a 100644 --- a/crates/bevy_render/src/texture/image_loader.rs +++ b/crates/bevy_render/src/texture/image_loader.rs @@ -1,5 +1,6 @@ use bevy_asset::{io::Reader, AssetLoader, AsyncReadExt, LoadContext}; use bevy_ecs::prelude::{FromWorld, World}; +use bevy_log::warn; use thiserror::Error; use crate::{ @@ -18,6 +19,8 @@ pub struct ImageLoader { } pub(crate) const IMG_FILE_EXTENSIONS: &[&str] = &[ + // special extension that doesn't map to any particular format. useful for loading assets of unknown format with ImageFormatSetting::FromContent + "image", #[cfg(feature = "basis-universal")] "basis", #[cfg(feature = "bmp")] @@ -117,6 +120,14 @@ impl AssetLoader for ImageLoader { error: TextureError::InvalidImageContentType(image_crate_format), path: load_context.path().display().to_string(), })?; + + // validate that the file format matches the content + if let Some(ext_format) = ImageFormat::from_extension(ext) { + if ext_format != format { + warn!("mismatched format for {}, filename extension `{}` has content of type {:?}", load_context.path().display(), ext, format); + } + } + ImageType::Format(format) } ImageFormatSetting::FromExtension => ImageType::Extension(ext), From 880878230691a0fc96a9e7320346489cef6fae3a Mon Sep 17 00:00:00 2001 From: robtfm <50659922+robtfm@users.noreply.github.com> Date: Thu, 22 Feb 2024 00:50:11 +0000 Subject: [PATCH 6/6] use TryFrom --- crates/bevy_render/src/texture/image.rs | 30 ++++++++++--------- .../bevy_render/src/texture/image_loader.rs | 5 ++-- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/crates/bevy_render/src/texture/image.rs b/crates/bevy_render/src/texture/image.rs index 698c6159a714d..3654af26699de 100644 --- a/crates/bevy_render/src/texture/image.rs +++ b/crates/bevy_render/src/texture/image.rs @@ -85,12 +85,13 @@ impl ImageFormat { _ => return None, }) } +} - /// Attempts to convert to [`image::ImageFormat`]. - /// - /// Unsupported formats will become [`None`]. - pub fn as_image_crate_format(&self) -> Option { - Some(match self { +impl TryFrom for image::ImageFormat { + type Error = (); + + fn try_from(value: ImageFormat) -> Result { + Ok(match value { ImageFormat::Avif => image::ImageFormat::Avif, ImageFormat::Bmp => image::ImageFormat::Bmp, ImageFormat::Dds => image::ImageFormat::Dds, @@ -105,15 +106,16 @@ impl ImageFormat { ImageFormat::Tga => image::ImageFormat::Tga, ImageFormat::Tiff => image::ImageFormat::Tiff, ImageFormat::WebP => image::ImageFormat::WebP, - ImageFormat::Basis | ImageFormat::Ktx2 => return None, + ImageFormat::Basis | ImageFormat::Ktx2 => return Err(()), }) } +} - /// Attempts to convert from [`image::ImageFormat`]. - /// - /// Unsupported formats will become [`None`]. - pub fn from_image_crate_format(format: image::ImageFormat) -> Option { - Some(match format { +impl TryFrom for ImageFormat { + type Error = (); + + fn try_from(value: image::ImageFormat) -> Result { + Ok(match value { image::ImageFormat::Avif => ImageFormat::Avif, image::ImageFormat::Bmp => ImageFormat::Bmp, image::ImageFormat::Dds => ImageFormat::Dds, @@ -128,7 +130,7 @@ impl ImageFormat { image::ImageFormat::Tga => ImageFormat::Tga, image::ImageFormat::Tiff => ImageFormat::Tiff, image::ImageFormat::WebP => ImageFormat::WebP, - _ => return None, + _ => return Err(()), }) } } @@ -710,8 +712,8 @@ impl Image { } _ => { let image_crate_format = format - .as_image_crate_format() - .ok_or_else(|| TextureError::UnsupportedTextureFormat(format!("{format:?}")))?; + .try_into() + .map_err(|_| TextureError::UnsupportedTextureFormat(format!("{format:?}")))?; let mut reader = image::io::Reader::new(std::io::Cursor::new(buffer)); reader.set_format(image_crate_format); reader.no_limits(); diff --git a/crates/bevy_render/src/texture/image_loader.rs b/crates/bevy_render/src/texture/image_loader.rs index 5e22467fdfb2a..da46cbe195972 100644 --- a/crates/bevy_render/src/texture/image_loader.rs +++ b/crates/bevy_render/src/texture/image_loader.rs @@ -115,8 +115,9 @@ impl AssetLoader for ImageLoader { path: load_context.path().display().to_string(), })?; - let format = ImageFormat::from_image_crate_format(image_crate_format) - .ok_or_else(|| FileTextureError { + let format = image_crate_format + .try_into() + .map_err(|_| FileTextureError { error: TextureError::InvalidImageContentType(image_crate_format), path: load_context.path().display().to_string(), })?;