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)]
143 pub texture_format: Option<wgpu_types::TextureFormat>,
144 pub is_srgb: bool,
148 pub sampler: ImageSampler,
151 pub asset_usage: RenderAssetUsages,
154 #[serde(default)]
158 pub array_layout: Option<ImageArrayLayout>,
159}
160
161impl Default for ImageLoaderSettings {
162 fn default() -> Self {
163 Self {
164 format: ImageFormatSetting::default(),
165 texture_format: None,
166 is_srgb: true,
167 sampler: ImageSampler::Default,
168 asset_usage: RenderAssetUsages::default(),
169 array_layout: None,
170 }
171 }
172}
173
174#[non_exhaustive]
176#[derive(Debug, Error)]
177pub enum ImageLoaderError {
178 #[error("Failed to load image bytes: {0}")]
180 Io(#[from] std::io::Error),
181 #[error("Could not load texture file: {0}")]
183 FileTexture(#[from] FileTextureError),
184 #[error("Invalid array layout: {0}")]
186 ArrayLayout(#[from] TextureReinterpretationError),
187}
188
189impl AssetLoader for ImageLoader {
190 type Asset = Image;
191 type Settings = ImageLoaderSettings;
192 type Error = ImageLoaderError;
193 async fn load(
194 &self,
195 reader: &mut dyn Reader,
196 settings: &ImageLoaderSettings,
197 load_context: &mut LoadContext<'_>,
198 ) -> Result<Image, Self::Error> {
199 let mut bytes = Vec::new();
200 reader.read_to_end(&mut bytes).await?;
201 let image_type = match settings.format {
202 ImageFormatSetting::FromExtension => {
203 let ext = load_context
205 .path()
206 .path()
207 .extension()
208 .unwrap()
209 .to_str()
210 .unwrap();
211 ImageType::Extension(ext)
212 }
213 ImageFormatSetting::Format(format) => ImageType::Format(format),
214 ImageFormatSetting::Guess => {
215 let format = image::guess_format(&bytes).map_err(|err| FileTextureError {
216 error: err.into(),
217 path: format!("{}", load_context.path().path().display()),
218 })?;
219 ImageType::Format(ImageFormat::from_image_crate_format(format).ok_or_else(
220 || FileTextureError {
221 error: TextureError::UnsupportedTextureFormat(format!("{format:?}")),
222 path: format!("{}", load_context.path().path().display()),
223 },
224 )?)
225 }
226 };
227
228 let mut image = Image::from_buffer(
229 &bytes,
230 image_type,
231 self.supported_compressed_formats,
232 settings.is_srgb,
233 settings.sampler.clone(),
234 settings.asset_usage,
235 )
236 .map_err(|err| FileTextureError {
237 error: err,
238 path: format!("{}", load_context.path().path().display()),
239 })?;
240
241 if let Some(format) = settings.texture_format {
242 image.texture_descriptor.format = format;
243 }
244
245 if let Some(array_layout) = settings.array_layout {
246 let image = match array_layout {
247 ImageArrayLayout::RowCount { rows } => {
248 image.reinterpret_stacked_2d_as_array(rows)?;
249 image
250 }
251 ImageArrayLayout::RowHeight { pixels } => {
252 image.reinterpret_stacked_2d_as_array(image.height() / pixels)?;
253 image
254 }
255 ImageArrayLayout::GridCount { columns, rows } => {
256 image.create_stacked_array_from_2d_grid(rows, columns)?
257 }
258 ImageArrayLayout::GridSize {
259 tile_width_pixels,
260 tile_height_pixels,
261 } => image.create_stacked_array_from_2d_grid(
262 image.height() / tile_height_pixels,
263 image.width() / tile_width_pixels,
264 )?,
265 };
266 return Ok(image);
267 }
268 Ok(image)
269 }
270
271 fn extensions(&self) -> &[&str] {
272 Self::SUPPORTED_FILE_EXTENSIONS
273 }
274}
275
276#[derive(Error, Debug)]
278#[error("Error reading image file {path}: {error}.")]
279pub struct FileTextureError {
280 error: TextureError,
281 path: String,
282}