bevy_render/texture/
image_texture_conversion.rs1use crate::{
2 render_asset::RenderAssetUsages,
3 texture::{Image, TextureFormatPixelInfo},
4};
5use image::{DynamicImage, ImageBuffer};
6use thiserror::Error;
7use wgpu::{Extent3d, TextureDimension, TextureFormat};
8
9impl Image {
10 pub fn from_dynamic(
12 dyn_img: DynamicImage,
13 is_srgb: bool,
14 asset_usage: RenderAssetUsages,
15 ) -> Image {
16 use bytemuck::cast_slice;
17 let width;
18 let height;
19
20 let data: Vec<u8>;
21 let format: TextureFormat;
22
23 match dyn_img {
24 DynamicImage::ImageLuma8(image) => {
25 let i = DynamicImage::ImageLuma8(image).into_rgba8();
26 width = i.width();
27 height = i.height();
28 format = if is_srgb {
29 TextureFormat::Rgba8UnormSrgb
30 } else {
31 TextureFormat::Rgba8Unorm
32 };
33
34 data = i.into_raw();
35 }
36 DynamicImage::ImageLumaA8(image) => {
37 let i = DynamicImage::ImageLumaA8(image).into_rgba8();
38 width = i.width();
39 height = i.height();
40 format = if is_srgb {
41 TextureFormat::Rgba8UnormSrgb
42 } else {
43 TextureFormat::Rgba8Unorm
44 };
45
46 data = i.into_raw();
47 }
48 DynamicImage::ImageRgb8(image) => {
49 let i = DynamicImage::ImageRgb8(image).into_rgba8();
50 width = i.width();
51 height = i.height();
52 format = if is_srgb {
53 TextureFormat::Rgba8UnormSrgb
54 } else {
55 TextureFormat::Rgba8Unorm
56 };
57
58 data = i.into_raw();
59 }
60 DynamicImage::ImageRgba8(image) => {
61 width = image.width();
62 height = image.height();
63 format = if is_srgb {
64 TextureFormat::Rgba8UnormSrgb
65 } else {
66 TextureFormat::Rgba8Unorm
67 };
68
69 data = image.into_raw();
70 }
71 DynamicImage::ImageLuma16(image) => {
72 width = image.width();
73 height = image.height();
74 format = TextureFormat::R16Uint;
75
76 let raw_data = image.into_raw();
77
78 data = cast_slice(&raw_data).to_owned();
79 }
80 DynamicImage::ImageLumaA16(image) => {
81 width = image.width();
82 height = image.height();
83 format = TextureFormat::Rg16Uint;
84
85 let raw_data = image.into_raw();
86
87 data = cast_slice(&raw_data).to_owned();
88 }
89 DynamicImage::ImageRgb16(image) => {
90 let i = DynamicImage::ImageRgb16(image).into_rgba16();
91 width = i.width();
92 height = i.height();
93 format = TextureFormat::Rgba16Unorm;
94
95 let raw_data = i.into_raw();
96
97 data = cast_slice(&raw_data).to_owned();
98 }
99 DynamicImage::ImageRgba16(image) => {
100 width = image.width();
101 height = image.height();
102 format = TextureFormat::Rgba16Unorm;
103
104 let raw_data = image.into_raw();
105
106 data = cast_slice(&raw_data).to_owned();
107 }
108 DynamicImage::ImageRgb32F(image) => {
109 width = image.width();
110 height = image.height();
111 format = TextureFormat::Rgba32Float;
112
113 let mut local_data =
114 Vec::with_capacity(width as usize * height as usize * format.pixel_size());
115
116 for pixel in image.into_raw().chunks_exact(3) {
117 let r = pixel[0];
120 let g = pixel[1];
121 let b = pixel[2];
122 let a = 1f32;
123
124 local_data.extend_from_slice(&r.to_ne_bytes());
125 local_data.extend_from_slice(&g.to_ne_bytes());
126 local_data.extend_from_slice(&b.to_ne_bytes());
127 local_data.extend_from_slice(&a.to_ne_bytes());
128 }
129
130 data = local_data;
131 }
132 DynamicImage::ImageRgba32F(image) => {
133 width = image.width();
134 height = image.height();
135 format = TextureFormat::Rgba32Float;
136
137 let raw_data = image.into_raw();
138
139 data = cast_slice(&raw_data).to_owned();
140 }
141 _ => {
143 let image = dyn_img.into_rgba8();
144 width = image.width();
145 height = image.height();
146 format = TextureFormat::Rgba8UnormSrgb;
147
148 data = image.into_raw();
149 }
150 }
151
152 Image::new(
153 Extent3d {
154 width,
155 height,
156 depth_or_array_layers: 1,
157 },
158 TextureDimension::D2,
159 data,
160 format,
161 asset_usage,
162 )
163 }
164
165 pub fn try_into_dynamic(self) -> Result<DynamicImage, IntoDynamicImageError> {
175 match self.texture_descriptor.format {
176 TextureFormat::R8Unorm => ImageBuffer::from_raw(self.width(), self.height(), self.data)
177 .map(DynamicImage::ImageLuma8),
178 TextureFormat::Rg8Unorm => {
179 ImageBuffer::from_raw(self.width(), self.height(), self.data)
180 .map(DynamicImage::ImageLumaA8)
181 }
182 TextureFormat::Rgba8UnormSrgb => {
183 ImageBuffer::from_raw(self.width(), self.height(), self.data)
184 .map(DynamicImage::ImageRgba8)
185 }
186 TextureFormat::Bgra8UnormSrgb | TextureFormat::Bgra8Unorm => {
189 ImageBuffer::from_raw(self.width(), self.height(), {
190 let mut data = self.data;
191 for bgra in data.chunks_exact_mut(4) {
192 bgra.swap(0, 2);
193 }
194 data
195 })
196 .map(DynamicImage::ImageRgba8)
197 }
198 texture_format => return Err(IntoDynamicImageError::UnsupportedFormat(texture_format)),
200 }
201 .ok_or(IntoDynamicImageError::UnknownConversionError(
202 self.texture_descriptor.format,
203 ))
204 }
205}
206
207#[non_exhaustive]
209#[derive(Error, Debug)]
210pub enum IntoDynamicImageError {
211 #[error("Conversion into dynamic image not supported for {0:?}.")]
213 UnsupportedFormat(TextureFormat),
214
215 #[error("Failed to convert into {0:?}.")]
217 UnknownConversionError(TextureFormat),
218}
219
220#[cfg(test)]
221mod test {
222 use image::{GenericImage, Rgba};
223
224 use super::*;
225
226 #[test]
227 fn two_way_conversion() {
228 let mut initial = DynamicImage::new_rgba8(1, 1);
230 initial.put_pixel(0, 0, Rgba::from([132, 3, 7, 200]));
231
232 let image = Image::from_dynamic(initial.clone(), true, RenderAssetUsages::RENDER_WORLD);
233
234 assert_eq!(initial, image.try_into_dynamic().unwrap());
236 }
237}