Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_image/src/image_loader.rs
6595 views
1
use crate::image::{Image, ImageFormat, ImageType, TextureError};
2
use bevy_asset::{io::Reader, AssetLoader, LoadContext, RenderAssetUsages};
3
use thiserror::Error;
4
5
use super::{CompressedImageFormats, ImageSampler};
6
use serde::{Deserialize, Serialize};
7
8
/// Loader for images that can be read by the `image` crate.
9
#[derive(Clone)]
10
pub struct ImageLoader {
11
supported_compressed_formats: CompressedImageFormats,
12
}
13
14
impl ImageLoader {
15
/// Full list of supported formats.
16
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
/// Total count of file extensions, for computing supported file extensions list.
48
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
/// Gets the list of file extensions for all formats.
59
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
/// Creates a new image loader that supports the provided formats.
77
pub fn new(supported_compressed_formats: CompressedImageFormats) -> Self {
78
Self {
79
supported_compressed_formats,
80
}
81
}
82
}
83
84
/// How to determine an image's format when loading.
85
#[derive(Serialize, Deserialize, Default, Debug, Clone)]
86
pub enum ImageFormatSetting {
87
/// Determine the image format from its file extension.
88
///
89
/// This is the default.
90
#[default]
91
FromExtension,
92
/// Declare the image format explicitly.
93
Format(ImageFormat),
94
/// Guess the image format by looking for magic bytes at the
95
/// beginning of its data.
96
Guess,
97
}
98
99
/// Settings for loading an [`Image`] using an [`ImageLoader`].
100
#[derive(Serialize, Deserialize, Debug, Clone)]
101
pub struct ImageLoaderSettings {
102
/// How to determine the image's container format.
103
pub format: ImageFormatSetting,
104
/// Forcibly use a specific [`wgpu_types::TextureFormat`].
105
/// Useful to control how data is handled when used
106
/// in a shader.
107
/// Ex: data that would be `R16Uint` that needs to
108
/// be sampled as a float using `R16Snorm`.
109
#[serde(skip)]
110
pub texture_format: Option<wgpu_types::TextureFormat>,
111
/// Specifies whether image data is linear
112
/// or in sRGB space when this is not determined by
113
/// the image format.
114
pub is_srgb: bool,
115
/// [`ImageSampler`] to use when rendering - this does
116
/// not affect the loading of the image data.
117
pub sampler: ImageSampler,
118
/// Where the asset will be used - see the docs on
119
/// [`RenderAssetUsages`] for details.
120
pub asset_usage: RenderAssetUsages,
121
}
122
123
impl Default for ImageLoaderSettings {
124
fn default() -> Self {
125
Self {
126
format: ImageFormatSetting::default(),
127
texture_format: None,
128
is_srgb: true,
129
sampler: ImageSampler::Default,
130
asset_usage: RenderAssetUsages::default(),
131
}
132
}
133
}
134
135
/// An error when loading an image using [`ImageLoader`].
136
#[non_exhaustive]
137
#[derive(Debug, Error)]
138
pub enum ImageLoaderError {
139
/// An error occurred while trying to load the image bytes.
140
#[error("Failed to load image bytes: {0}")]
141
Io(#[from] std::io::Error),
142
/// An error occurred while trying to decode the image bytes.
143
#[error("Could not load texture file: {0}")]
144
FileTexture(#[from] FileTextureError),
145
}
146
147
impl AssetLoader for ImageLoader {
148
type Asset = Image;
149
type Settings = ImageLoaderSettings;
150
type Error = ImageLoaderError;
151
async fn load(
152
&self,
153
reader: &mut dyn Reader,
154
settings: &ImageLoaderSettings,
155
load_context: &mut LoadContext<'_>,
156
) -> Result<Image, Self::Error> {
157
let mut bytes = Vec::new();
158
reader.read_to_end(&mut bytes).await?;
159
let image_type = match settings.format {
160
ImageFormatSetting::FromExtension => {
161
// use the file extension for the image type
162
let ext = load_context.path().extension().unwrap().to_str().unwrap();
163
ImageType::Extension(ext)
164
}
165
ImageFormatSetting::Format(format) => ImageType::Format(format),
166
ImageFormatSetting::Guess => {
167
let format = image::guess_format(&bytes).map_err(|err| FileTextureError {
168
error: err.into(),
169
path: format!("{}", load_context.path().display()),
170
})?;
171
ImageType::Format(ImageFormat::from_image_crate_format(format).ok_or_else(
172
|| FileTextureError {
173
error: TextureError::UnsupportedTextureFormat(format!("{format:?}")),
174
path: format!("{}", load_context.path().display()),
175
},
176
)?)
177
}
178
};
179
Ok(Image::from_buffer(
180
&bytes,
181
image_type,
182
self.supported_compressed_formats,
183
settings.is_srgb,
184
settings.sampler.clone(),
185
settings.asset_usage,
186
)
187
.map(|mut image| {
188
if let Some(format) = settings.texture_format {
189
image.texture_descriptor.format = format;
190
}
191
image
192
})
193
.map_err(|err| FileTextureError {
194
error: err,
195
path: format!("{}", load_context.path().display()),
196
})?)
197
}
198
199
fn extensions(&self) -> &[&str] {
200
Self::SUPPORTED_FILE_EXTENSIONS
201
}
202
}
203
204
/// An error that occurs when loading a texture from a file.
205
#[derive(Error, Debug)]
206
#[error("Error reading image file {path}: {error}.")]
207
pub struct FileTextureError {
208
error: TextureError,
209
path: String,
210
}
211
212