Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_image/src/image_texture_conversion.rs
6595 views
1
use crate::{Image, TextureFormatPixelInfo};
2
use bevy_asset::RenderAssetUsages;
3
use image::{DynamicImage, ImageBuffer};
4
use thiserror::Error;
5
use wgpu_types::{Extent3d, TextureDimension, TextureFormat};
6
7
impl Image {
8
/// Converts a [`DynamicImage`] to an [`Image`].
9
pub fn from_dynamic(
10
dyn_img: DynamicImage,
11
is_srgb: bool,
12
asset_usage: RenderAssetUsages,
13
) -> Image {
14
use bytemuck::cast_slice;
15
let width;
16
let height;
17
18
let data: Vec<u8>;
19
let format: TextureFormat;
20
21
match dyn_img {
22
DynamicImage::ImageLuma8(image) => {
23
let i = DynamicImage::ImageLuma8(image).into_rgba8();
24
width = i.width();
25
height = i.height();
26
format = if is_srgb {
27
TextureFormat::Rgba8UnormSrgb
28
} else {
29
TextureFormat::Rgba8Unorm
30
};
31
32
data = i.into_raw();
33
}
34
DynamicImage::ImageLumaA8(image) => {
35
let i = DynamicImage::ImageLumaA8(image).into_rgba8();
36
width = i.width();
37
height = i.height();
38
format = if is_srgb {
39
TextureFormat::Rgba8UnormSrgb
40
} else {
41
TextureFormat::Rgba8Unorm
42
};
43
44
data = i.into_raw();
45
}
46
DynamicImage::ImageRgb8(image) => {
47
let i = DynamicImage::ImageRgb8(image).into_rgba8();
48
width = i.width();
49
height = i.height();
50
format = if is_srgb {
51
TextureFormat::Rgba8UnormSrgb
52
} else {
53
TextureFormat::Rgba8Unorm
54
};
55
56
data = i.into_raw();
57
}
58
DynamicImage::ImageRgba8(image) => {
59
width = image.width();
60
height = image.height();
61
format = if is_srgb {
62
TextureFormat::Rgba8UnormSrgb
63
} else {
64
TextureFormat::Rgba8Unorm
65
};
66
67
data = image.into_raw();
68
}
69
DynamicImage::ImageLuma16(image) => {
70
width = image.width();
71
height = image.height();
72
format = TextureFormat::R16Uint;
73
74
let raw_data = image.into_raw();
75
76
data = cast_slice(&raw_data).to_owned();
77
}
78
DynamicImage::ImageLumaA16(image) => {
79
width = image.width();
80
height = image.height();
81
format = TextureFormat::Rg16Uint;
82
83
let raw_data = image.into_raw();
84
85
data = cast_slice(&raw_data).to_owned();
86
}
87
DynamicImage::ImageRgb16(image) => {
88
let i = DynamicImage::ImageRgb16(image).into_rgba16();
89
width = i.width();
90
height = i.height();
91
format = TextureFormat::Rgba16Unorm;
92
93
let raw_data = i.into_raw();
94
95
data = cast_slice(&raw_data).to_owned();
96
}
97
DynamicImage::ImageRgba16(image) => {
98
width = image.width();
99
height = image.height();
100
format = TextureFormat::Rgba16Unorm;
101
102
let raw_data = image.into_raw();
103
104
data = cast_slice(&raw_data).to_owned();
105
}
106
DynamicImage::ImageRgb32F(image) => {
107
width = image.width();
108
height = image.height();
109
format = TextureFormat::Rgba32Float;
110
111
let mut local_data = Vec::with_capacity(
112
width as usize * height as usize * format.pixel_size().unwrap_or(0),
113
);
114
115
for pixel in image.into_raw().chunks_exact(3) {
116
// TODO: use the array_chunks method once stabilized
117
// https://github.com/rust-lang/rust/issues/74985
118
let r = pixel[0];
119
let g = pixel[1];
120
let b = pixel[2];
121
let a = 1f32;
122
123
local_data.extend_from_slice(&r.to_le_bytes());
124
local_data.extend_from_slice(&g.to_le_bytes());
125
local_data.extend_from_slice(&b.to_le_bytes());
126
local_data.extend_from_slice(&a.to_le_bytes());
127
}
128
129
data = local_data;
130
}
131
DynamicImage::ImageRgba32F(image) => {
132
width = image.width();
133
height = image.height();
134
format = TextureFormat::Rgba32Float;
135
136
let raw_data = image.into_raw();
137
138
data = cast_slice(&raw_data).to_owned();
139
}
140
// DynamicImage is now non exhaustive, catch future variants and convert them
141
_ => {
142
let image = dyn_img.into_rgba8();
143
width = image.width();
144
height = image.height();
145
format = TextureFormat::Rgba8UnormSrgb;
146
147
data = image.into_raw();
148
}
149
}
150
151
Image::new(
152
Extent3d {
153
width,
154
height,
155
depth_or_array_layers: 1,
156
},
157
TextureDimension::D2,
158
data,
159
format,
160
asset_usage,
161
)
162
}
163
164
/// Convert a [`Image`] to a [`DynamicImage`]. Useful for editing image
165
/// data. Not all [`TextureFormat`] are covered, therefore it will return an
166
/// error if the format is unsupported. Supported formats are:
167
/// - `TextureFormat::R8Unorm`
168
/// - `TextureFormat::Rg8Unorm`
169
/// - `TextureFormat::Rgba8UnormSrgb`
170
/// - `TextureFormat::Bgra8UnormSrgb`
171
///
172
/// To convert [`Image`] to a different format see: [`Image::convert`].
173
pub fn try_into_dynamic(self) -> Result<DynamicImage, IntoDynamicImageError> {
174
let width = self.width();
175
let height = self.height();
176
let Some(data) = self.data else {
177
return Err(IntoDynamicImageError::UninitializedImage);
178
};
179
match self.texture_descriptor.format {
180
TextureFormat::R8Unorm => {
181
ImageBuffer::from_raw(width, height, data).map(DynamicImage::ImageLuma8)
182
}
183
TextureFormat::Rg8Unorm => {
184
ImageBuffer::from_raw(width, height, data).map(DynamicImage::ImageLumaA8)
185
}
186
TextureFormat::Rgba8UnormSrgb => {
187
ImageBuffer::from_raw(width, height, data).map(DynamicImage::ImageRgba8)
188
}
189
// This format is commonly used as the format for the swapchain texture
190
// This conversion is added here to support screenshots
191
TextureFormat::Bgra8UnormSrgb | TextureFormat::Bgra8Unorm => {
192
ImageBuffer::from_raw(width, height, {
193
let mut data = data;
194
for bgra in data.chunks_exact_mut(4) {
195
bgra.swap(0, 2);
196
}
197
data
198
})
199
.map(DynamicImage::ImageRgba8)
200
}
201
// Throw and error if conversion isn't supported
202
texture_format => return Err(IntoDynamicImageError::UnsupportedFormat(texture_format)),
203
}
204
.ok_or(IntoDynamicImageError::UnknownConversionError(
205
self.texture_descriptor.format,
206
))
207
}
208
}
209
210
/// Errors that occur while converting an [`Image`] into a [`DynamicImage`]
211
#[non_exhaustive]
212
#[derive(Error, Debug)]
213
pub enum IntoDynamicImageError {
214
/// Conversion into dynamic image not supported for source format.
215
#[error("Conversion into dynamic image not supported for {0:?}.")]
216
UnsupportedFormat(TextureFormat),
217
218
/// Encountered an unknown error during conversion.
219
#[error("Failed to convert into {0:?}.")]
220
UnknownConversionError(TextureFormat),
221
222
/// Tried to convert an image that has no texture data
223
#[error("Image has no texture data")]
224
UninitializedImage,
225
}
226
227
#[cfg(test)]
228
mod test {
229
use image::{GenericImage, Rgba};
230
231
use super::*;
232
233
#[test]
234
fn two_way_conversion() {
235
// Check to see if color is preserved through an rgba8 conversion and back.
236
let mut initial = DynamicImage::new_rgba8(1, 1);
237
initial.put_pixel(0, 0, Rgba::from([132, 3, 7, 200]));
238
239
let image = Image::from_dynamic(initial.clone(), true, RenderAssetUsages::RENDER_WORLD);
240
241
// NOTE: Fails if `is_srgb = false` or the dynamic image is of the type rgb8.
242
assert_eq!(initial, image.try_into_dynamic().unwrap());
243
}
244
}
245
246