bevy_image/
image_texture_conversion.rs1use crate::{Image, TextureFormatPixelInfo};
2use bevy_asset::RenderAssetUsages;
3use image::{DynamicImage, ImageBuffer};
4use thiserror::Error;
5use wgpu_types::{Extent3d, TextureDimension, TextureFormat};
6
7impl Image {
8 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 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 _ => {
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 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 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 texture_format => return Err(IntoDynamicImageError::UnsupportedFormat(texture_format)),
203 }
204 .ok_or(IntoDynamicImageError::UnknownConversionError(
205 self.texture_descriptor.format,
206 ))
207 }
208}
209
210#[non_exhaustive]
212#[derive(Error, Debug)]
213pub enum IntoDynamicImageError {
214 #[error("Conversion into dynamic image not supported for {0:?}.")]
216 UnsupportedFormat(TextureFormat),
217
218 #[error("Failed to convert into {0:?}.")]
220 UnknownConversionError(TextureFormat),
221
222 #[error("Image has no texture data")]
224 UninitializedImage,
225}
226
227#[cfg(test)]
228mod test {
229 use image::{GenericImage, Rgba};
230
231 use super::*;
232
233 #[test]
234 fn two_way_conversion() {
235 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 assert_eq!(initial, image.try_into_dynamic().unwrap());
243 }
244}