1use crate::{
2 image::{Image, ImageFormat, ImageType, TextureError},
3 TextureReinterpretationError,
4};
5use bevy_asset::{io::Reader, AssetLoader, LoadContext, RenderAssetUsages};
6use bevy_reflect::TypePath;
7use thiserror::Error;
8
9use super::{CompressedImageFormats, ImageSampler};
10use serde::{Deserialize, Serialize};
11
12#[derive(Clone, TypePath)]
14pub struct ImageLoader {
15 supported_compressed_formats: CompressedImageFormats,
16}
17
18impl ImageLoader {
19 pub const SUPPORTED_FORMATS: &'static [ImageFormat] = &[
21 #[cfg(feature = "basis-universal")]
22 ImageFormat::Basis,
23 #[cfg(feature = "bmp")]
24 ImageFormat::Bmp,
25 #[cfg(feature = "dds")]
26 ImageFormat::Dds,
27 #[cfg(feature = "ff")]
28 ImageFormat::Farbfeld,
29 #[cfg(feature = "gif")]
30 ImageFormat::Gif,
31 #[cfg(feature = "ico")]
32 ImageFormat::Ico,
33 #[cfg(feature = "jpeg")]
34 ImageFormat::Jpeg,
35 #[cfg(feature = "ktx2")]
36 ImageFormat::Ktx2,
37 #[cfg(feature = "png")]
38 ImageFormat::Png,
39 #[cfg(feature = "pnm")]
40 ImageFormat::Pnm,
41 #[cfg(feature = "qoi")]
42 ImageFormat::Qoi,
43 #[cfg(feature = "tga")]
44 ImageFormat::Tga,
45 #[cfg(feature = "tiff")]
46 ImageFormat::Tiff,
47 #[cfg(feature = "webp")]
48 ImageFormat::WebP,
49 ];
50
51 const COUNT_FILE_EXTENSIONS: usize = {
53 let mut count = 0;
54 let mut idx = 0;
55 while idx < Self::SUPPORTED_FORMATS.len() {
56 count += Self::SUPPORTED_FORMATS[idx].to_file_extensions().len();
57 idx += 1;
58 }
59 count
60 };
61
62 pub const SUPPORTED_FILE_EXTENSIONS: &'static [&'static str] = &{
64 let mut exts = [""; Self::COUNT_FILE_EXTENSIONS];
65 let mut ext_idx = 0;
66 let mut fmt_idx = 0;
67 while fmt_idx < Self::SUPPORTED_FORMATS.len() {
68 let mut off = 0;
69 let fmt_exts = Self::SUPPORTED_FORMATS[fmt_idx].to_file_extensions();
70 while off < fmt_exts.len() {
71 exts[ext_idx] = fmt_exts[off];
72 off += 1;
73 ext_idx += 1;
74 }
75 fmt_idx += 1;
76 }
77 exts
78 };
79
80 pub fn new(supported_compressed_formats: CompressedImageFormats) -> Self {
82 Self {
83 supported_compressed_formats,
84 }
85 }
86}
87
88#[derive(Serialize, Deserialize, Default, Debug, Clone)]
90pub enum ImageFormatSetting {
91 #[default]
95 FromExtension,
96 Format(ImageFormat),
98 Guess,
101}
102
103#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
105pub enum ImageArrayLayout {
106 RowCount {
108 rows: u32,
110 },
111 RowHeight {
113 pixels: u32,
115 },
116 GridCount {
118 columns: u32,
120 rows: u32,
122 },
123 GridSize {
125 tile_width_pixels: u32,
127 tile_height_pixels: u32,
129 },
130}
131
132#[derive(Serialize, Deserialize, Debug, Clone)]
134pub struct ImageLoaderSettings {
135 pub format: ImageFormatSetting,
137 #[serde(default)]
144 pub texture_format: Option<wgpu_types::TextureFormat>,
145 pub is_srgb: bool,
149 pub sampler: ImageSampler,
152 pub asset_usage: RenderAssetUsages,
155 #[serde(default)]
159 pub array_layout: Option<ImageArrayLayout>,
160 #[serde(default = "default_expand_grayscale")]
164 pub expand_grayscale: bool,
165}
166
167fn default_expand_grayscale() -> bool {
168 true
169}
170
171impl Default for ImageLoaderSettings {
172 fn default() -> Self {
173 Self {
174 format: ImageFormatSetting::default(),
175 texture_format: None,
176 is_srgb: true,
177 sampler: ImageSampler::Default,
178 asset_usage: RenderAssetUsages::default(),
179 array_layout: None,
180 expand_grayscale: true,
181 }
182 }
183}
184
185#[non_exhaustive]
187#[derive(Debug, Error)]
188pub enum ImageLoaderError {
189 #[error("Failed to load image bytes: {0}")]
191 Io(#[from] std::io::Error),
192 #[error("Could not load texture file: {0}")]
194 FileTexture(#[from] FileTextureError),
195 #[error("Invalid array layout: {0}")]
197 ArrayLayout(#[from] TextureReinterpretationError),
198}
199
200impl AssetLoader for ImageLoader {
201 type Asset = Image;
202 type Settings = ImageLoaderSettings;
203 type Error = ImageLoaderError;
204 async fn load(
205 &self,
206 reader: &mut dyn Reader,
207 settings: &ImageLoaderSettings,
208 load_context: &mut LoadContext<'_>,
209 ) -> Result<Image, Self::Error> {
210 let mut bytes = Vec::new();
211 reader.read_to_end(&mut bytes).await?;
212 let image_type = match settings.format {
213 ImageFormatSetting::FromExtension => {
214 let ext = load_context
216 .path()
217 .path()
218 .extension()
219 .unwrap()
220 .to_str()
221 .unwrap();
222 ImageType::Extension(ext)
223 }
224 ImageFormatSetting::Format(format) => ImageType::Format(format),
225 ImageFormatSetting::Guess => {
226 let format = image::guess_format(&bytes).map_err(|err| FileTextureError {
227 error: err.into(),
228 path: format!("{}", load_context.path().path().display()),
229 })?;
230 ImageType::Format(ImageFormat::from_image_crate_format(format).ok_or_else(
231 || FileTextureError {
232 error: TextureError::UnsupportedTextureFormat(format!("{format:?}")),
233 path: format!("{}", load_context.path().path().display()),
234 },
235 )?)
236 }
237 };
238
239 let expand_grayscale = settings.expand_grayscale
240 && !settings.texture_format.is_some_and(|f| f.components() <= 2);
241 let mut image = Image::from_buffer_inner(
242 &bytes,
243 image_type,
244 self.supported_compressed_formats,
245 settings.is_srgb,
246 expand_grayscale,
247 settings.sampler.clone(),
248 settings.asset_usage,
249 )
250 .map_err(|err| FileTextureError {
251 error: err,
252 path: format!("{}", load_context.path().path().display()),
253 })?;
254
255 if let Some(format) = settings.texture_format {
256 image.texture_descriptor.format = format;
257 }
258
259 if let Some(array_layout) = settings.array_layout {
260 let image = match array_layout {
261 ImageArrayLayout::RowCount { rows } => {
262 image.reinterpret_stacked_2d_as_array(rows)?;
263 image
264 }
265 ImageArrayLayout::RowHeight { pixels } => {
266 image.reinterpret_stacked_2d_as_array(image.height() / pixels)?;
267 image
268 }
269 ImageArrayLayout::GridCount { columns, rows } => {
270 image.create_stacked_array_from_2d_grid(rows, columns)?
271 }
272 ImageArrayLayout::GridSize {
273 tile_width_pixels,
274 tile_height_pixels,
275 } => image.create_stacked_array_from_2d_grid(
276 image.height() / tile_height_pixels,
277 image.width() / tile_width_pixels,
278 )?,
279 };
280 return Ok(image);
281 }
282 Ok(image)
283 }
284
285 fn extensions(&self) -> &[&str] {
286 Self::SUPPORTED_FILE_EXTENSIONS
287 }
288}
289
290#[derive(Error, Debug)]
292#[error("Error reading image file {path}: {error}.")]
293pub struct FileTextureError {
294 error: TextureError,
295 path: String,
296}
297
298#[cfg(test)]
299mod tests {
300 use super::ImageLoaderSettings;
301
302 #[test]
303 fn missing_expand_grayscale_defaults_to_true() {
304 let mut value = serde_json::to_value(ImageLoaderSettings::default()).unwrap();
305 value.as_object_mut().unwrap().remove("expand_grayscale");
306 let settings: ImageLoaderSettings = serde_json::from_value(value).unwrap();
307 assert!(settings.expand_grayscale);
308 }
309}