From 7c8106da904e3002f2b65918338fc8b80e2fc189 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Mockers?= Date: Tue, 16 Feb 2021 03:30:53 +0100 Subject: [PATCH 1/4] common way to load texture from bytes with either extension or mime type --- .../src/texture/image_texture_loader.rs | 96 ++++++++++++++----- 1 file changed, 73 insertions(+), 23 deletions(-) diff --git a/crates/bevy_render/src/texture/image_texture_loader.rs b/crates/bevy_render/src/texture/image_texture_loader.rs index 7b7c824f7d64e..da1a477ea9963 100644 --- a/crates/bevy_render/src/texture/image_texture_loader.rs +++ b/crates/bevy_render/src/texture/image_texture_loader.rs @@ -1,7 +1,8 @@ -use super::image_texture_conversion::image_to_texture; +use super::{image_texture_conversion::image_to_texture, Texture}; use anyhow::Result; use bevy_asset::{AssetLoader, LoadContext, LoadedAsset}; use bevy_utils::BoxedFuture; +use thiserror::Error; /// Loader for images that can be read by the `image` crate. #[derive(Clone, Default)] @@ -16,30 +17,17 @@ impl AssetLoader for ImageTextureLoader { load_context: &'a mut LoadContext, ) -> BoxedFuture<'a, Result<()>> { Box::pin(async move { - // Find the image type we expect. A file with the extension "png" should - // probably load as a PNG. - + // use the file extension for the image type let ext = load_context.path().extension().unwrap().to_str().unwrap(); - let img_format = image::ImageFormat::from_extension(ext) - .ok_or_else(|| { - format!( - "Unexpected image format {:?} for file {}, this is an error in `bevy_render`.", - ext, - load_context.path().display() - ) - }) - .unwrap(); - - // Load the image in the expected format. - // Some formats like PNG allow for R or RG textures too, so the texture - // format needs to be determined. For RGB textures an alpha channel - // needs to be added, so the image data needs to be converted in those - // cases. - - let dyn_img = image::load_from_memory_with_format(bytes, img_format)?; - - load_context.set_default_asset(LoadedAsset::new(image_to_texture(dyn_img))); + let dyn_img = buffer_to_texture(bytes, ImageType::Extension(ext)).map_err(|err| { + FileTextureError { + error: err, + path: format!("{}", load_context.path().display()), + } + })?; + + load_context.set_default_asset(LoadedAsset::new(dyn_img)); Ok(()) }) } @@ -49,6 +37,68 @@ impl AssetLoader for ImageTextureLoader { } } +/// An error that occurs when loading a texture from a file +#[derive(Error, Debug)] +pub struct FileTextureError { + error: TextureError, + path: String, +} +impl std::fmt::Display for FileTextureError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { + write!( + f, + "Error reading image file {}: {}, this is an error in `bevy_render`.", + self.path, self.error + ) + } +} + +/// An error that occurs when loading a texture +#[derive(Error, Debug)] +pub enum TextureError { + #[error("invalid image mime type")] + InvalidImageMimeType(String), + #[error("invalid image extension")] + InvalidImageExtension(String), + #[error("failed to load an image")] + ImageError(#[from] image::ImageError), +} + +/// Type of a raw image buffer +pub enum ImageType<'a> { + /// Mime type of an image, for example `"image/png"` + MimeType(&'a str), + /// Extension of an image file, for example `"png"` + Extension(&'a str), +} + +/// Load a bytes buffer in a [`Texture`], according to type `image_type`, using the `image` crate` +pub fn buffer_to_texture(buffer: &[u8], image_type: ImageType) -> Result { + let format = match image_type { + ImageType::MimeType(mime_type) => match mime_type { + "image/png" => Ok(image::ImageFormat::Png), + "image/vnd-ms.dds" => Ok(image::ImageFormat::Dds), + "image/x-targa" => Ok(image::ImageFormat::Tga), + "image/x-tga" => Ok(image::ImageFormat::Tga), + "image/jpeg" => Ok(image::ImageFormat::Jpeg), + "image/bmp" => Ok(image::ImageFormat::Bmp), + "image/x-bmp" => Ok(image::ImageFormat::Bmp), + _ => Err(TextureError::InvalidImageMimeType(mime_type.to_string())), + }, + ImageType::Extension(extension) => image::ImageFormat::from_extension(extension) + .ok_or_else(|| TextureError::InvalidImageMimeType(extension.to_string())), + }?; + + // Load the image in the expected format. + // Some formats like PNG allow for R or RG textures too, so the texture + // format needs to be determined. For RGB textures an alpha channel + // needs to be added, so the image data needs to be converted in those + // cases. + + let dyn_img = image::load_from_memory_with_format(buffer, format)?; + Ok(image_to_texture(dyn_img)) +} + #[cfg(test)] mod tests { use super::*; From e59aa046a47faa24b8193dc8d7f1e76b1a511c13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Mockers?= Date: Tue, 16 Feb 2021 03:31:28 +0100 Subject: [PATCH 2/4] load texture using bevy_render rather than directly using image --- crates/bevy_gltf/Cargo.toml | 1 - crates/bevy_gltf/src/loader.rs | 33 ++++++++------------------------- 2 files changed, 8 insertions(+), 26 deletions(-) diff --git a/crates/bevy_gltf/Cargo.toml b/crates/bevy_gltf/Cargo.toml index c0ef7d52ca64a..5e8d1ad72b480 100644 --- a/crates/bevy_gltf/Cargo.toml +++ b/crates/bevy_gltf/Cargo.toml @@ -27,7 +27,6 @@ bevy_scene = { path = "../bevy_scene", version = "0.4.0" } # other gltf = { version = "0.15.2", default-features = false, features = ["utils", "names", "KHR_materials_unlit"] } -image = { version = "0.23.12", default-features = false } thiserror = "1.0" anyhow = "1.0" base64 = "0.13.0" diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index 68a84fb02fb5a..597a1abecd802 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -13,7 +13,7 @@ use bevy_render::{ prelude::{Color, Texture}, render_graph::base, texture::{ - AddressMode, Extent3d, FilterMode, SamplerDescriptor, TextureDimension, TextureFormat, + buffer_to_texture, AddressMode, FilterMode, ImageType, SamplerDescriptor, TextureError, }, }; use bevy_scene::Scene; @@ -26,7 +26,6 @@ use gltf::{ texture::{MagFilter, MinFilter, WrappingMode}, Material, Primitive, }; -use image::{GenericImageView, ImageFormat}; use std::{collections::HashMap, path::Path}; use thiserror::Error; @@ -50,7 +49,7 @@ pub enum GltfError { #[error("invalid image mime type")] InvalidImageMimeType(String), #[error("failed to load an image")] - ImageError(#[from] image::ImageError), + ImageError(#[from] TextureError), #[error("failed to load an asset path")] AssetIoError(#[from] AssetIoError), } @@ -193,31 +192,15 @@ async fn load_gltf<'a, 'b>( }) .collect(); - for texture in gltf.textures() { - if let gltf::image::Source::View { view, mime_type } = texture.source().source() { + for gltf_texture in gltf.textures() { + if let gltf::image::Source::View { view, mime_type } = gltf_texture.source().source() { let start = view.offset() as usize; let end = (view.offset() + view.length()) as usize; let buffer = &buffer_data[view.buffer().index()][start..end]; - let format = match mime_type { - "image/png" => Ok(ImageFormat::Png), - "image/jpeg" => Ok(ImageFormat::Jpeg), - _ => Err(GltfError::InvalidImageMimeType(mime_type.to_string())), - }?; - let image = image::load_from_memory_with_format(buffer, format)?; - let size = image.dimensions(); - let image = image.into_rgba8(); - - let texture_label = texture_label(&texture); - load_context.set_labeled_asset::( - &texture_label, - LoadedAsset::new(Texture { - data: image.clone().into_vec(), - size: Extent3d::new(size.0, size.1, 1), - dimension: TextureDimension::D2, - format: TextureFormat::Rgba8Unorm, - sampler: texture_sampler(&texture)?, - }), - ); + let texture_label = texture_label(&gltf_texture); + let mut texture = buffer_to_texture(buffer, ImageType::MimeType(mime_type))?; + texture.sampler = texture_sampler(&gltf_texture)?; + load_context.set_labeled_asset::(&texture_label, LoadedAsset::new(texture)); } } From 92de25926612d0dc207c2a66be5b8e9b30dcc83f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Mockers?= Date: Thu, 18 Feb 2021 22:41:40 +0100 Subject: [PATCH 3/4] remove feature check for mod image_texture_loader --- crates/bevy_render/src/texture/mod.rs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/crates/bevy_render/src/texture/mod.rs b/crates/bevy_render/src/texture/mod.rs index d943dad2c5c0d..0116754b7fda3 100644 --- a/crates/bevy_render/src/texture/mod.rs +++ b/crates/bevy_render/src/texture/mod.rs @@ -1,12 +1,5 @@ #[cfg(feature = "hdr")] mod hdr_texture_loader; -#[cfg(any( - feature = "png", - feature = "dds", - feature = "tga", - feature = "jpeg", - feature = "bmp" -))] mod image_texture_loader; mod sampler_descriptor; #[allow(clippy::module_inception)] @@ -18,13 +11,6 @@ pub(crate) mod image_texture_conversion; #[cfg(feature = "hdr")] pub use hdr_texture_loader::*; -#[cfg(any( - feature = "png", - feature = "dds", - feature = "tga", - feature = "jpeg", - feature = "bmp" -))] pub use image_texture_loader::*; pub use sampler_descriptor::*; pub use texture::*; From a8b1b6052a01ac3a87631d48db9f4852397ae0b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Mockers?= Date: Wed, 3 Mar 2021 22:33:28 +0100 Subject: [PATCH 4/4] move buffer_to_texture to Texture::from_buffer --- crates/bevy_gltf/src/loader.rs | 6 +- .../src/texture/image_texture_loader.rs | 61 +++---------------- crates/bevy_render/src/texture/texture.rs | 52 +++++++++++++++- 3 files changed, 61 insertions(+), 58 deletions(-) diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index 597a1abecd802..941b6512098ea 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -12,9 +12,7 @@ use bevy_render::{ pipeline::PrimitiveTopology, prelude::{Color, Texture}, render_graph::base, - texture::{ - buffer_to_texture, AddressMode, FilterMode, ImageType, SamplerDescriptor, TextureError, - }, + texture::{AddressMode, FilterMode, ImageType, SamplerDescriptor, TextureError}, }; use bevy_scene::Scene; use bevy_transform::{ @@ -198,7 +196,7 @@ async fn load_gltf<'a, 'b>( let end = (view.offset() + view.length()) as usize; let buffer = &buffer_data[view.buffer().index()][start..end]; let texture_label = texture_label(&gltf_texture); - let mut texture = buffer_to_texture(buffer, ImageType::MimeType(mime_type))?; + let mut texture = Texture::from_buffer(buffer, ImageType::MimeType(mime_type))?; texture.sampler = texture_sampler(&gltf_texture)?; load_context.set_labeled_asset::(&texture_label, LoadedAsset::new(texture)); } diff --git a/crates/bevy_render/src/texture/image_texture_loader.rs b/crates/bevy_render/src/texture/image_texture_loader.rs index da1a477ea9963..1525c6330ebdf 100644 --- a/crates/bevy_render/src/texture/image_texture_loader.rs +++ b/crates/bevy_render/src/texture/image_texture_loader.rs @@ -1,4 +1,4 @@ -use super::{image_texture_conversion::image_to_texture, Texture}; +use super::texture::{ImageType, Texture, TextureError}; use anyhow::Result; use bevy_asset::{AssetLoader, LoadContext, LoadedAsset}; use bevy_utils::BoxedFuture; @@ -20,12 +20,13 @@ impl AssetLoader for ImageTextureLoader { // use the file extension for the image type let ext = load_context.path().extension().unwrap().to_str().unwrap(); - let dyn_img = buffer_to_texture(bytes, ImageType::Extension(ext)).map_err(|err| { - FileTextureError { - error: err, - path: format!("{}", load_context.path().display()), - } - })?; + let dyn_img = + Texture::from_buffer(bytes, ImageType::Extension(ext)).map_err(|err| { + FileTextureError { + error: err, + path: format!("{}", load_context.path().display()), + } + })?; load_context.set_default_asset(LoadedAsset::new(dyn_img)); Ok(()) @@ -53,52 +54,6 @@ impl std::fmt::Display for FileTextureError { } } -/// An error that occurs when loading a texture -#[derive(Error, Debug)] -pub enum TextureError { - #[error("invalid image mime type")] - InvalidImageMimeType(String), - #[error("invalid image extension")] - InvalidImageExtension(String), - #[error("failed to load an image")] - ImageError(#[from] image::ImageError), -} - -/// Type of a raw image buffer -pub enum ImageType<'a> { - /// Mime type of an image, for example `"image/png"` - MimeType(&'a str), - /// Extension of an image file, for example `"png"` - Extension(&'a str), -} - -/// Load a bytes buffer in a [`Texture`], according to type `image_type`, using the `image` crate` -pub fn buffer_to_texture(buffer: &[u8], image_type: ImageType) -> Result { - let format = match image_type { - ImageType::MimeType(mime_type) => match mime_type { - "image/png" => Ok(image::ImageFormat::Png), - "image/vnd-ms.dds" => Ok(image::ImageFormat::Dds), - "image/x-targa" => Ok(image::ImageFormat::Tga), - "image/x-tga" => Ok(image::ImageFormat::Tga), - "image/jpeg" => Ok(image::ImageFormat::Jpeg), - "image/bmp" => Ok(image::ImageFormat::Bmp), - "image/x-bmp" => Ok(image::ImageFormat::Bmp), - _ => Err(TextureError::InvalidImageMimeType(mime_type.to_string())), - }, - ImageType::Extension(extension) => image::ImageFormat::from_extension(extension) - .ok_or_else(|| TextureError::InvalidImageMimeType(extension.to_string())), - }?; - - // Load the image in the expected format. - // Some formats like PNG allow for R or RG textures too, so the texture - // format needs to be determined. For RGB textures an alpha channel - // needs to be added, so the image data needs to be converted in those - // cases. - - let dyn_img = image::load_from_memory_with_format(buffer, format)?; - Ok(image_to_texture(dyn_img)) -} - #[cfg(test)] mod tests { use super::*; diff --git a/crates/bevy_render/src/texture/texture.rs b/crates/bevy_render/src/texture/texture.rs index a7536f7dc5181..20939fc068a2e 100644 --- a/crates/bevy_render/src/texture/texture.rs +++ b/crates/bevy_render/src/texture/texture.rs @@ -1,4 +1,7 @@ -use super::{Extent3d, SamplerDescriptor, TextureDescriptor, TextureDimension, TextureFormat}; +use super::{ + image_texture_conversion::image_to_texture, Extent3d, SamplerDescriptor, TextureDescriptor, + TextureDimension, TextureFormat, +}; use crate::renderer::{ RenderResource, RenderResourceContext, RenderResourceId, RenderResourceType, }; @@ -7,6 +10,7 @@ use bevy_asset::{AssetEvent, Assets, Handle}; use bevy_ecs::Res; use bevy_reflect::TypeUuid; use bevy_utils::HashSet; +use thiserror::Error; pub const TEXTURE_ASSET_INDEX: u64 = 0; pub const SAMPLER_ASSET_INDEX: u64 = 1; @@ -212,6 +216,33 @@ impl Texture { render_resource_context.remove_asset_resource(handle, SAMPLER_ASSET_INDEX); } } + + /// Load a bytes buffer in a [`Texture`], according to type `image_type`, using the `image` crate` + pub fn from_buffer(buffer: &[u8], image_type: ImageType) -> Result { + let format = match image_type { + ImageType::MimeType(mime_type) => match mime_type { + "image/png" => Ok(image::ImageFormat::Png), + "image/vnd-ms.dds" => Ok(image::ImageFormat::Dds), + "image/x-targa" => Ok(image::ImageFormat::Tga), + "image/x-tga" => Ok(image::ImageFormat::Tga), + "image/jpeg" => Ok(image::ImageFormat::Jpeg), + "image/bmp" => Ok(image::ImageFormat::Bmp), + "image/x-bmp" => Ok(image::ImageFormat::Bmp), + _ => Err(TextureError::InvalidImageMimeType(mime_type.to_string())), + }, + ImageType::Extension(extension) => image::ImageFormat::from_extension(extension) + .ok_or_else(|| TextureError::InvalidImageMimeType(extension.to_string())), + }?; + + // Load the image in the expected format. + // Some formats like PNG allow for R or RG textures too, so the texture + // format needs to be determined. For RGB textures an alpha channel + // needs to be added, so the image data needs to be converted in those + // cases. + + let dyn_img = image::load_from_memory_with_format(buffer, format)?; + Ok(image_to_texture(dyn_img)) + } } impl RenderResource for Option> { @@ -245,3 +276,22 @@ impl RenderResource for Handle { Some(self) } } + +/// An error that occurs when loading a texture +#[derive(Error, Debug)] +pub enum TextureError { + #[error("invalid image mime type")] + InvalidImageMimeType(String), + #[error("invalid image extension")] + InvalidImageExtension(String), + #[error("failed to load an image")] + ImageError(#[from] image::ImageError), +} + +/// Type of a raw image buffer +pub enum ImageType<'a> { + /// Mime type of an image, for example `"image/png"` + MimeType(&'a str), + /// Extension of an image file, for example `"png"` + Extension(&'a str), +}