Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_image/src/image_loader.rs
9334 views
1
use crate::{
2
image::{Image, ImageFormat, ImageType, TextureError},
3
TextureReinterpretationError,
4
};
5
use bevy_asset::{io::Reader, AssetLoader, LoadContext, RenderAssetUsages};
6
use bevy_reflect::TypePath;
7
use thiserror::Error;
8
9
use super::{CompressedImageFormats, ImageSampler};
10
use serde::{Deserialize, Serialize};
11
12
/// Loader for images that can be read by the `image` crate.
13
#[derive(Clone, TypePath)]
14
pub struct ImageLoader {
15
supported_compressed_formats: CompressedImageFormats,
16
}
17
18
impl ImageLoader {
19
/// Full list of supported formats.
20
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
/// Total count of file extensions, for computing supported file extensions list.
52
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
/// Gets the list of file extensions for all formats.
63
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
/// Creates a new image loader that supports the provided formats.
81
pub fn new(supported_compressed_formats: CompressedImageFormats) -> Self {
82
Self {
83
supported_compressed_formats,
84
}
85
}
86
}
87
88
/// How to determine an image's format when loading.
89
#[derive(Serialize, Deserialize, Default, Debug, Clone)]
90
pub enum ImageFormatSetting {
91
/// Determine the image format from its file extension.
92
///
93
/// This is the default.
94
#[default]
95
FromExtension,
96
/// Declare the image format explicitly.
97
Format(ImageFormat),
98
/// Guess the image format by looking for magic bytes at the
99
/// beginning of its data.
100
Guess,
101
}
102
103
/// How to interpret the image as an array of textures.
104
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
105
pub enum ImageArrayLayout {
106
/// Interpret the image as a vertical stack of *n* images.
107
RowCount { rows: u32 },
108
/// Interpret the image as a vertical stack of images, each *n* pixels tall.
109
RowHeight { pixels: u32 },
110
}
111
112
/// Settings for loading an [`Image`] using an [`ImageLoader`].
113
#[derive(Serialize, Deserialize, Debug, Clone)]
114
pub struct ImageLoaderSettings {
115
/// How to determine the image's container format.
116
pub format: ImageFormatSetting,
117
/// Forcibly use a specific [`wgpu_types::TextureFormat`].
118
/// Useful to control how data is handled when used
119
/// in a shader.
120
/// Ex: data that would be `R16Uint` that needs to
121
/// be sampled as a float using `R16Snorm`.
122
#[serde(default)]
123
pub texture_format: Option<wgpu_types::TextureFormat>,
124
/// Specifies whether image data is linear
125
/// or in sRGB space when this is not determined by
126
/// the image format.
127
pub is_srgb: bool,
128
/// [`ImageSampler`] to use when rendering - this does
129
/// not affect the loading of the image data.
130
pub sampler: ImageSampler,
131
/// Where the asset will be used - see the docs on
132
/// [`RenderAssetUsages`] for details.
133
pub asset_usage: RenderAssetUsages,
134
/// Interpret the image as an array of images. This is
135
/// primarily for use with the `texture2DArray` shader
136
/// uniform type.
137
#[serde(default)]
138
pub array_layout: Option<ImageArrayLayout>,
139
}
140
141
impl 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
/// An error when loading an image using [`ImageLoader`].
155
#[non_exhaustive]
156
#[derive(Debug, Error)]
157
pub enum ImageLoaderError {
158
/// An error occurred while trying to load the image bytes.
159
#[error("Failed to load image bytes: {0}")]
160
Io(#[from] std::io::Error),
161
/// An error occurred while trying to decode the image bytes.
162
#[error("Could not load texture file: {0}")]
163
FileTexture(#[from] FileTextureError),
164
/// An error occurred while trying to interpret the image bytes as an array texture.
165
#[error("Invalid array layout: {0}")]
166
ArrayLayout(#[from] TextureReinterpretationError),
167
}
168
169
impl 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
// use the file extension for the image type
184
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
/// An error that occurs when loading a texture from a file.
243
#[derive(Error, Debug)]
244
#[error("Error reading image file {path}: {error}.")]
245
pub struct FileTextureError {
246
error: TextureError,
247
path: String,
248
}
249
250