bevy_image/
image_loader.rs

1use crate::image::{Image, ImageFormat, ImageType, TextureError};
2use bevy_asset::{io::Reader, AssetLoader, LoadContext, RenderAssetUsages};
3use derive_more::derive::{Display, Error, From};
4
5use super::{CompressedImageFormats, ImageSampler};
6use serde::{Deserialize, Serialize};
7
8/// Loader for images that can be read by the `image` crate.
9#[derive(Clone)]
10pub struct ImageLoader {
11    supported_compressed_formats: CompressedImageFormats,
12}
13
14impl ImageLoader {
15    /// Full list of supported formats.
16    pub const SUPPORTED_FORMATS: &'static [ImageFormat] = &[
17        #[cfg(feature = "basis-universal")]
18        ImageFormat::Basis,
19        #[cfg(feature = "bmp")]
20        ImageFormat::Bmp,
21        #[cfg(feature = "dds")]
22        ImageFormat::Dds,
23        #[cfg(feature = "ff")]
24        ImageFormat::Farbfeld,
25        #[cfg(feature = "gif")]
26        ImageFormat::Gif,
27        #[cfg(feature = "ico")]
28        ImageFormat::Ico,
29        #[cfg(feature = "jpeg")]
30        ImageFormat::Jpeg,
31        #[cfg(feature = "ktx2")]
32        ImageFormat::Ktx2,
33        #[cfg(feature = "png")]
34        ImageFormat::Png,
35        #[cfg(feature = "pnm")]
36        ImageFormat::Pnm,
37        #[cfg(feature = "qoi")]
38        ImageFormat::Qoi,
39        #[cfg(feature = "tga")]
40        ImageFormat::Tga,
41        #[cfg(feature = "tiff")]
42        ImageFormat::Tiff,
43        #[cfg(feature = "webp")]
44        ImageFormat::WebP,
45    ];
46
47    /// Total count of file extensions, for computing supported file extensions list.
48    const COUNT_FILE_EXTENSIONS: usize = {
49        let mut count = 0;
50        let mut idx = 0;
51        while idx < Self::SUPPORTED_FORMATS.len() {
52            count += Self::SUPPORTED_FORMATS[idx].to_file_extensions().len();
53            idx += 1;
54        }
55        count
56    };
57
58    /// Gets the list of file extensions for all formats.
59    pub const SUPPORTED_FILE_EXTENSIONS: &'static [&'static str] = &{
60        let mut exts = [""; Self::COUNT_FILE_EXTENSIONS];
61        let mut ext_idx = 0;
62        let mut fmt_idx = 0;
63        while fmt_idx < Self::SUPPORTED_FORMATS.len() {
64            let mut off = 0;
65            let fmt_exts = Self::SUPPORTED_FORMATS[fmt_idx].to_file_extensions();
66            while off < fmt_exts.len() {
67                exts[ext_idx] = fmt_exts[off];
68                off += 1;
69                ext_idx += 1;
70            }
71            fmt_idx += 1;
72        }
73        exts
74    };
75
76    /// Creates a new image loader that supports the provided formats.
77    pub fn new(supported_compressed_formats: CompressedImageFormats) -> Self {
78        Self {
79            supported_compressed_formats,
80        }
81    }
82}
83
84#[derive(Serialize, Deserialize, Default, Debug)]
85pub enum ImageFormatSetting {
86    #[default]
87    FromExtension,
88    Format(ImageFormat),
89    Guess,
90}
91
92#[derive(Serialize, Deserialize, Debug)]
93pub struct ImageLoaderSettings {
94    pub format: ImageFormatSetting,
95    pub is_srgb: bool,
96    pub sampler: ImageSampler,
97    pub asset_usage: RenderAssetUsages,
98}
99
100impl Default for ImageLoaderSettings {
101    fn default() -> Self {
102        Self {
103            format: ImageFormatSetting::default(),
104            is_srgb: true,
105            sampler: ImageSampler::Default,
106            asset_usage: RenderAssetUsages::default(),
107        }
108    }
109}
110
111#[non_exhaustive]
112#[derive(Debug, Error, Display, From)]
113pub enum ImageLoaderError {
114    #[display("Could load shader: {_0}")]
115    Io(std::io::Error),
116    #[display("Could not load texture file: {_0}")]
117    FileTexture(FileTextureError),
118}
119
120impl AssetLoader for ImageLoader {
121    type Asset = Image;
122    type Settings = ImageLoaderSettings;
123    type Error = ImageLoaderError;
124    async fn load(
125        &self,
126        reader: &mut dyn Reader,
127        settings: &ImageLoaderSettings,
128        load_context: &mut LoadContext<'_>,
129    ) -> Result<Image, Self::Error> {
130        let mut bytes = Vec::new();
131        reader.read_to_end(&mut bytes).await?;
132        let image_type = match settings.format {
133            ImageFormatSetting::FromExtension => {
134                // use the file extension for the image type
135                let ext = load_context.path().extension().unwrap().to_str().unwrap();
136                ImageType::Extension(ext)
137            }
138            ImageFormatSetting::Format(format) => ImageType::Format(format),
139            ImageFormatSetting::Guess => {
140                let format = image::guess_format(&bytes).map_err(|err| FileTextureError {
141                    error: err.into(),
142                    path: format!("{}", load_context.path().display()),
143                })?;
144                ImageType::Format(ImageFormat::from_image_crate_format(format).ok_or_else(
145                    || FileTextureError {
146                        error: TextureError::UnsupportedTextureFormat(format!("{format:?}")),
147                        path: format!("{}", load_context.path().display()),
148                    },
149                )?)
150            }
151        };
152        Ok(Image::from_buffer(
153            #[cfg(all(debug_assertions, feature = "dds"))]
154            load_context.path().display().to_string(),
155            &bytes,
156            image_type,
157            self.supported_compressed_formats,
158            settings.is_srgb,
159            settings.sampler.clone(),
160            settings.asset_usage,
161        )
162        .map_err(|err| FileTextureError {
163            error: err,
164            path: format!("{}", load_context.path().display()),
165        })?)
166    }
167
168    fn extensions(&self) -> &[&str] {
169        Self::SUPPORTED_FILE_EXTENSIONS
170    }
171}
172
173/// An error that occurs when loading a texture from a file.
174#[derive(Error, Display, Debug)]
175#[display("Error reading image file {path}: {error}, this is an error in `bevy_render`.")]
176pub struct FileTextureError {
177    error: TextureError,
178    path: String,
179}