bevy_render/texture/
image_loader.rs

1use bevy_asset::{io::Reader, AssetLoader, AsyncReadExt, LoadContext};
2use bevy_ecs::prelude::{FromWorld, World};
3use thiserror::Error;
4
5use crate::{
6    render_asset::RenderAssetUsages,
7    renderer::RenderDevice,
8    texture::{Image, ImageFormat, ImageType, TextureError},
9};
10
11use super::{CompressedImageFormats, ImageSampler};
12use serde::{Deserialize, Serialize};
13
14/// Loader for images that can be read by the `image` crate.
15#[derive(Clone)]
16pub struct ImageLoader {
17    supported_compressed_formats: CompressedImageFormats,
18}
19
20pub(crate) const IMG_FILE_EXTENSIONS: &[&str] = &[
21    #[cfg(feature = "basis-universal")]
22    "basis",
23    #[cfg(feature = "bmp")]
24    "bmp",
25    #[cfg(feature = "png")]
26    "png",
27    #[cfg(feature = "dds")]
28    "dds",
29    #[cfg(feature = "tga")]
30    "tga",
31    #[cfg(feature = "jpeg")]
32    "jpg",
33    #[cfg(feature = "jpeg")]
34    "jpeg",
35    #[cfg(feature = "ktx2")]
36    "ktx2",
37    #[cfg(feature = "webp")]
38    "webp",
39    #[cfg(feature = "pnm")]
40    "pam",
41    #[cfg(feature = "pnm")]
42    "pbm",
43    #[cfg(feature = "pnm")]
44    "pgm",
45    #[cfg(feature = "pnm")]
46    "ppm",
47];
48
49#[derive(Serialize, Deserialize, Default, Debug)]
50pub enum ImageFormatSetting {
51    #[default]
52    FromExtension,
53    Format(ImageFormat),
54    Guess,
55}
56
57#[derive(Serialize, Deserialize, Debug)]
58pub struct ImageLoaderSettings {
59    pub format: ImageFormatSetting,
60    pub is_srgb: bool,
61    pub sampler: ImageSampler,
62    pub asset_usage: RenderAssetUsages,
63}
64
65impl Default for ImageLoaderSettings {
66    fn default() -> Self {
67        Self {
68            format: ImageFormatSetting::default(),
69            is_srgb: true,
70            sampler: ImageSampler::Default,
71            asset_usage: RenderAssetUsages::default(),
72        }
73    }
74}
75
76#[non_exhaustive]
77#[derive(Debug, Error)]
78pub enum ImageLoaderError {
79    #[error("Could load shader: {0}")]
80    Io(#[from] std::io::Error),
81    #[error("Could not load texture file: {0}")]
82    FileTexture(#[from] FileTextureError),
83}
84
85impl AssetLoader for ImageLoader {
86    type Asset = Image;
87    type Settings = ImageLoaderSettings;
88    type Error = ImageLoaderError;
89    async fn load<'a>(
90        &'a self,
91        reader: &'a mut Reader<'_>,
92        settings: &'a ImageLoaderSettings,
93        load_context: &'a mut LoadContext<'_>,
94    ) -> Result<Image, Self::Error> {
95        let mut bytes = Vec::new();
96        reader.read_to_end(&mut bytes).await?;
97        let image_type = match settings.format {
98            ImageFormatSetting::FromExtension => {
99                // use the file extension for the image type
100                let ext = load_context.path().extension().unwrap().to_str().unwrap();
101                ImageType::Extension(ext)
102            }
103            ImageFormatSetting::Format(format) => ImageType::Format(format),
104            ImageFormatSetting::Guess => {
105                let format = image::guess_format(&bytes).map_err(|err| FileTextureError {
106                    error: err.into(),
107                    path: format!("{}", load_context.path().display()),
108                })?;
109                ImageType::Format(ImageFormat::from_image_crate_format(format).ok_or_else(
110                    || FileTextureError {
111                        error: TextureError::UnsupportedTextureFormat(format!("{format:?}")),
112                        path: format!("{}", load_context.path().display()),
113                    },
114                )?)
115            }
116        };
117        Ok(Image::from_buffer(
118            #[cfg(all(debug_assertions, feature = "dds"))]
119            load_context.path().display().to_string(),
120            &bytes,
121            image_type,
122            self.supported_compressed_formats,
123            settings.is_srgb,
124            settings.sampler.clone(),
125            settings.asset_usage,
126        )
127        .map_err(|err| FileTextureError {
128            error: err,
129            path: format!("{}", load_context.path().display()),
130        })?)
131    }
132
133    fn extensions(&self) -> &[&str] {
134        IMG_FILE_EXTENSIONS
135    }
136}
137
138impl FromWorld for ImageLoader {
139    fn from_world(world: &mut World) -> Self {
140        let supported_compressed_formats = match world.get_resource::<RenderDevice>() {
141            Some(render_device) => CompressedImageFormats::from_features(render_device.features()),
142
143            None => CompressedImageFormats::NONE,
144        };
145        Self {
146            supported_compressed_formats,
147        }
148    }
149}
150
151/// An error that occurs when loading a texture from a file.
152#[derive(Error, Debug)]
153pub struct FileTextureError {
154    error: TextureError,
155    path: String,
156}
157impl std::fmt::Display for FileTextureError {
158    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
159        write!(
160            f,
161            "Error reading image file {}: {}, this is an error in `bevy_render`.",
162            self.path, self.error
163        )
164    }
165}