bevy_image/
image_loader.rs1use 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#[derive(Clone)]
10pub struct ImageLoader {
11 supported_compressed_formats: CompressedImageFormats,
12}
13
14impl ImageLoader {
15 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 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 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 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 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#[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}