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 { rows: u32 },
108 RowHeight { pixels: u32 },
110}
111
112#[derive(Serialize, Deserialize, Debug, Clone)]
114pub struct ImageLoaderSettings {
115 pub format: ImageFormatSetting,
117 #[serde(skip)]
123 pub texture_format: Option<wgpu_types::TextureFormat>,
124 pub is_srgb: bool,
128 pub sampler: ImageSampler,
131 pub asset_usage: RenderAssetUsages,
134 #[serde(default)]
138 pub array_layout: Option<ImageArrayLayout>,
139}
140
141impl Default for ImageLoaderSettings {
142 fn default() -> Self {
143 Self {
144 format: ImageFormatSetting::default(),
145 texture_format: None,
146 is_srgb: true,
147 sampler: ImageSampler::Default,
148 asset_usage: RenderAssetUsages::default(),
149 array_layout: None,
150 }
151 }
152}
153
154#[non_exhaustive]
156#[derive(Debug, Error)]
157pub enum ImageLoaderError {
158 #[error("Failed to load image bytes: {0}")]
160 Io(#[from] std::io::Error),
161 #[error("Could not load texture file: {0}")]
163 FileTexture(#[from] FileTextureError),
164 #[error("Invalid array layout: {0}")]
166 ArrayLayout(#[from] TextureReinterpretationError),
167}
168
169impl AssetLoader for ImageLoader {
170 type Asset = Image;
171 type Settings = ImageLoaderSettings;
172 type Error = ImageLoaderError;
173 async fn load(
174 &self,
175 reader: &mut dyn Reader,
176 settings: &ImageLoaderSettings,
177 load_context: &mut LoadContext<'_>,
178 ) -> Result<Image, Self::Error> {
179 let mut bytes = Vec::new();
180 reader.read_to_end(&mut bytes).await?;
181 let image_type = match settings.format {
182 ImageFormatSetting::FromExtension => {
183 let ext = load_context
185 .path()
186 .path()
187 .extension()
188 .unwrap()
189 .to_str()
190 .unwrap();
191 ImageType::Extension(ext)
192 }
193 ImageFormatSetting::Format(format) => ImageType::Format(format),
194 ImageFormatSetting::Guess => {
195 let format = image::guess_format(&bytes).map_err(|err| FileTextureError {
196 error: err.into(),
197 path: format!("{}", load_context.path().path().display()),
198 })?;
199 ImageType::Format(ImageFormat::from_image_crate_format(format).ok_or_else(
200 || FileTextureError {
201 error: TextureError::UnsupportedTextureFormat(format!("{format:?}")),
202 path: format!("{}", load_context.path().path().display()),
203 },
204 )?)
205 }
206 };
207
208 let mut image = Image::from_buffer(
209 &bytes,
210 image_type,
211 self.supported_compressed_formats,
212 settings.is_srgb,
213 settings.sampler.clone(),
214 settings.asset_usage,
215 )
216 .map_err(|err| FileTextureError {
217 error: err,
218 path: format!("{}", load_context.path().path().display()),
219 })?;
220
221 if let Some(format) = settings.texture_format {
222 image.texture_descriptor.format = format;
223 }
224
225 if let Some(array_layout) = settings.array_layout {
226 let layers = match array_layout {
227 ImageArrayLayout::RowCount { rows } => rows,
228 ImageArrayLayout::RowHeight { pixels } => image.height() / pixels,
229 };
230
231 image.reinterpret_stacked_2d_as_array(layers)?;
232 }
233
234 Ok(image)
235 }
236
237 fn extensions(&self) -> &[&str] {
238 Self::SUPPORTED_FILE_EXTENSIONS
239 }
240}
241
242#[derive(Error, Debug)]
244#[error("Error reading image file {path}: {error}.")]
245pub struct FileTextureError {
246 error: TextureError,
247 path: String,
248}