Skip to content

Commit 1fcafc4

Browse files
committed
Glb textures should use bevy_render to load images (#1454)
Fixes #1396 <img width="1392" alt="Screenshot 2021-02-16 at 02 24 01" src="https://user-images.githubusercontent.com/8672791/108011774-1b991a80-7008-11eb-979e-6ebfc51fba3c.png"> Issue was that, when loading an image directly from its bytes in the binary glb file, it didn't follow the same flow as when loaded as a texture file. This PR removes the dependency to `image` from `bevy_gltf`, and load the image using `bevy_render` in all cases. I also added support for more mime types while there. <img width="1392" alt="Screenshot 2021-02-16 at 02 44 56" src="https://user-images.githubusercontent.com/8672791/108011915-674bc400-7008-11eb-83d4-ded96a38919b.png">
1 parent 6e14ed2 commit 1fcafc4

File tree

5 files changed

+87
-66
lines changed

5 files changed

+87
-66
lines changed

crates/bevy_gltf/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ bevy_scene = { path = "../bevy_scene", version = "0.4.0" }
2727

2828
# other
2929
gltf = { version = "0.15.2", default-features = false, features = ["utils", "names", "KHR_materials_unlit"] }
30-
image = { version = "0.23.12", default-features = false }
3130
thiserror = "1.0"
3231
anyhow = "1.0"
3332
base64 = "0.13.0"

crates/bevy_gltf/src/loader.rs

Lines changed: 8 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,7 @@ use bevy_render::{
1212
pipeline::PrimitiveTopology,
1313
prelude::{Color, Texture},
1414
render_graph::base,
15-
texture::{
16-
AddressMode, Extent3d, FilterMode, SamplerDescriptor, TextureDimension, TextureFormat,
17-
},
15+
texture::{AddressMode, FilterMode, ImageType, SamplerDescriptor, TextureError},
1816
};
1917
use bevy_scene::Scene;
2018
use bevy_transform::{
@@ -26,7 +24,6 @@ use gltf::{
2624
texture::{MagFilter, MinFilter, WrappingMode},
2725
Material, Primitive,
2826
};
29-
use image::{GenericImageView, ImageFormat};
3027
use std::{collections::HashMap, path::Path};
3128
use thiserror::Error;
3229

@@ -50,7 +47,7 @@ pub enum GltfError {
5047
#[error("invalid image mime type")]
5148
InvalidImageMimeType(String),
5249
#[error("failed to load an image")]
53-
ImageError(#[from] image::ImageError),
50+
ImageError(#[from] TextureError),
5451
#[error("failed to load an asset path")]
5552
AssetIoError(#[from] AssetIoError),
5653
}
@@ -193,31 +190,15 @@ async fn load_gltf<'a, 'b>(
193190
})
194191
.collect();
195192

196-
for texture in gltf.textures() {
197-
if let gltf::image::Source::View { view, mime_type } = texture.source().source() {
193+
for gltf_texture in gltf.textures() {
194+
if let gltf::image::Source::View { view, mime_type } = gltf_texture.source().source() {
198195
let start = view.offset() as usize;
199196
let end = (view.offset() + view.length()) as usize;
200197
let buffer = &buffer_data[view.buffer().index()][start..end];
201-
let format = match mime_type {
202-
"image/png" => Ok(ImageFormat::Png),
203-
"image/jpeg" => Ok(ImageFormat::Jpeg),
204-
_ => Err(GltfError::InvalidImageMimeType(mime_type.to_string())),
205-
}?;
206-
let image = image::load_from_memory_with_format(buffer, format)?;
207-
let size = image.dimensions();
208-
let image = image.into_rgba8();
209-
210-
let texture_label = texture_label(&texture);
211-
load_context.set_labeled_asset::<Texture>(
212-
&texture_label,
213-
LoadedAsset::new(Texture {
214-
data: image.clone().into_vec(),
215-
size: Extent3d::new(size.0, size.1, 1),
216-
dimension: TextureDimension::D2,
217-
format: TextureFormat::Rgba8Unorm,
218-
sampler: texture_sampler(&texture)?,
219-
}),
220-
);
198+
let texture_label = texture_label(&gltf_texture);
199+
let mut texture = Texture::from_buffer(buffer, ImageType::MimeType(mime_type))?;
200+
texture.sampler = texture_sampler(&gltf_texture)?;
201+
load_context.set_labeled_asset::<Texture>(&texture_label, LoadedAsset::new(texture));
221202
}
222203
}
223204

crates/bevy_render/src/texture/image_texture_loader.rs

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
use super::image_texture_conversion::image_to_texture;
1+
use super::texture::{ImageType, Texture, TextureError};
22
use anyhow::Result;
33
use bevy_asset::{AssetLoader, LoadContext, LoadedAsset};
44
use bevy_utils::BoxedFuture;
5+
use thiserror::Error;
56

67
/// Loader for images that can be read by the `image` crate.
78
#[derive(Clone, Default)]
@@ -16,30 +17,18 @@ impl AssetLoader for ImageTextureLoader {
1617
load_context: &'a mut LoadContext,
1718
) -> BoxedFuture<'a, Result<()>> {
1819
Box::pin(async move {
19-
// Find the image type we expect. A file with the extension "png" should
20-
// probably load as a PNG.
21-
20+
// use the file extension for the image type
2221
let ext = load_context.path().extension().unwrap().to_str().unwrap();
2322

24-
let img_format = image::ImageFormat::from_extension(ext)
25-
.ok_or_else(|| {
26-
format!(
27-
"Unexpected image format {:?} for file {}, this is an error in `bevy_render`.",
28-
ext,
29-
load_context.path().display()
30-
)
31-
})
32-
.unwrap();
33-
34-
// Load the image in the expected format.
35-
// Some formats like PNG allow for R or RG textures too, so the texture
36-
// format needs to be determined. For RGB textures an alpha channel
37-
// needs to be added, so the image data needs to be converted in those
38-
// cases.
39-
40-
let dyn_img = image::load_from_memory_with_format(bytes, img_format)?;
41-
42-
load_context.set_default_asset(LoadedAsset::new(image_to_texture(dyn_img)));
23+
let dyn_img =
24+
Texture::from_buffer(bytes, ImageType::Extension(ext)).map_err(|err| {
25+
FileTextureError {
26+
error: err,
27+
path: format!("{}", load_context.path().display()),
28+
}
29+
})?;
30+
31+
load_context.set_default_asset(LoadedAsset::new(dyn_img));
4332
Ok(())
4433
})
4534
}
@@ -49,6 +38,22 @@ impl AssetLoader for ImageTextureLoader {
4938
}
5039
}
5140

41+
/// An error that occurs when loading a texture from a file
42+
#[derive(Error, Debug)]
43+
pub struct FileTextureError {
44+
error: TextureError,
45+
path: String,
46+
}
47+
impl std::fmt::Display for FileTextureError {
48+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
49+
write!(
50+
f,
51+
"Error reading image file {}: {}, this is an error in `bevy_render`.",
52+
self.path, self.error
53+
)
54+
}
55+
}
56+
5257
#[cfg(test)]
5358
mod tests {
5459
use super::*;

crates/bevy_render/src/texture/mod.rs

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,5 @@
11
#[cfg(feature = "hdr")]
22
mod hdr_texture_loader;
3-
#[cfg(any(
4-
feature = "png",
5-
feature = "dds",
6-
feature = "tga",
7-
feature = "jpeg",
8-
feature = "bmp"
9-
))]
103
mod image_texture_loader;
114
mod sampler_descriptor;
125
#[allow(clippy::module_inception)]
@@ -18,13 +11,6 @@ pub(crate) mod image_texture_conversion;
1811

1912
#[cfg(feature = "hdr")]
2013
pub use hdr_texture_loader::*;
21-
#[cfg(any(
22-
feature = "png",
23-
feature = "dds",
24-
feature = "tga",
25-
feature = "jpeg",
26-
feature = "bmp"
27-
))]
2814
pub use image_texture_loader::*;
2915
pub use sampler_descriptor::*;
3016
pub use texture::*;

crates/bevy_render/src/texture/texture.rs

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
use super::{Extent3d, SamplerDescriptor, TextureDescriptor, TextureDimension, TextureFormat};
1+
use super::{
2+
image_texture_conversion::image_to_texture, Extent3d, SamplerDescriptor, TextureDescriptor,
3+
TextureDimension, TextureFormat,
4+
};
25
use crate::renderer::{
36
RenderResource, RenderResourceContext, RenderResourceId, RenderResourceType,
47
};
@@ -7,6 +10,7 @@ use bevy_asset::{AssetEvent, Assets, Handle};
710
use bevy_ecs::Res;
811
use bevy_reflect::TypeUuid;
912
use bevy_utils::HashSet;
13+
use thiserror::Error;
1014

1115
pub const TEXTURE_ASSET_INDEX: u64 = 0;
1216
pub const SAMPLER_ASSET_INDEX: u64 = 1;
@@ -212,6 +216,33 @@ impl Texture {
212216
render_resource_context.remove_asset_resource(handle, SAMPLER_ASSET_INDEX);
213217
}
214218
}
219+
220+
/// Load a bytes buffer in a [`Texture`], according to type `image_type`, using the `image` crate`
221+
pub fn from_buffer(buffer: &[u8], image_type: ImageType) -> Result<Texture, TextureError> {
222+
let format = match image_type {
223+
ImageType::MimeType(mime_type) => match mime_type {
224+
"image/png" => Ok(image::ImageFormat::Png),
225+
"image/vnd-ms.dds" => Ok(image::ImageFormat::Dds),
226+
"image/x-targa" => Ok(image::ImageFormat::Tga),
227+
"image/x-tga" => Ok(image::ImageFormat::Tga),
228+
"image/jpeg" => Ok(image::ImageFormat::Jpeg),
229+
"image/bmp" => Ok(image::ImageFormat::Bmp),
230+
"image/x-bmp" => Ok(image::ImageFormat::Bmp),
231+
_ => Err(TextureError::InvalidImageMimeType(mime_type.to_string())),
232+
},
233+
ImageType::Extension(extension) => image::ImageFormat::from_extension(extension)
234+
.ok_or_else(|| TextureError::InvalidImageMimeType(extension.to_string())),
235+
}?;
236+
237+
// Load the image in the expected format.
238+
// Some formats like PNG allow for R or RG textures too, so the texture
239+
// format needs to be determined. For RGB textures an alpha channel
240+
// needs to be added, so the image data needs to be converted in those
241+
// cases.
242+
243+
let dyn_img = image::load_from_memory_with_format(buffer, format)?;
244+
Ok(image_to_texture(dyn_img))
245+
}
215246
}
216247

217248
impl RenderResource for Option<Handle<Texture>> {
@@ -245,3 +276,22 @@ impl RenderResource for Handle<Texture> {
245276
Some(self)
246277
}
247278
}
279+
280+
/// An error that occurs when loading a texture
281+
#[derive(Error, Debug)]
282+
pub enum TextureError {
283+
#[error("invalid image mime type")]
284+
InvalidImageMimeType(String),
285+
#[error("invalid image extension")]
286+
InvalidImageExtension(String),
287+
#[error("failed to load an image")]
288+
ImageError(#[from] image::ImageError),
289+
}
290+
291+
/// Type of a raw image buffer
292+
pub enum ImageType<'a> {
293+
/// Mime type of an image, for example `"image/png"`
294+
MimeType(&'a str),
295+
/// Extension of an image file, for example `"png"`
296+
Extension(&'a str),
297+
}

0 commit comments

Comments
 (0)