1use glam::UVec2;
5use image::EncodableLayout;
6use snafu::prelude::*;
7
8fn cwd() -> Option<String> {
9 #[cfg(target_arch = "wasm32")]
10 {
11 Some("localhost".to_string())
12 }
13 #[cfg(not(target_arch = "wasm32"))]
14 {
15 let cwd = std::env::current_dir().ok()?;
16 Some(format!("{}", cwd.display()))
17 }
18}
19
20#[derive(Debug, Snafu)]
21pub enum AtlasImageError {
22 #[snafu(display("Cannot load image '{}' from cwd '{:?}': {source}", path.display(), cwd()))]
23 CannotLoad {
24 source: std::io::Error,
25 path: std::path::PathBuf,
26 },
27
28 #[snafu(display("Image error: {source}\nCurrent dir: {:?}", cwd()))]
29 Image { source: image::error::ImageError },
30}
31
32#[derive(Clone, Copy, Debug)]
33pub enum AtlasImageFormat {
34 R8,
35 R8G8,
36 R8G8B8,
37 R8G8B8A8,
38 R16,
39 R16G16,
40 R16G16B16,
41 R16G16B16A16,
42 R16G16B16A16FLOAT,
43 R32FLOAT,
44 R32G32B32FLOAT,
45 R32G32B32A32FLOAT,
46 D32FLOAT,
47}
48
49impl From<AtlasImageFormat> for wgpu::TextureFormat {
50 fn from(value: AtlasImageFormat) -> Self {
51 match value {
52 AtlasImageFormat::R8 => wgpu::TextureFormat::R8Unorm,
53 AtlasImageFormat::R8G8 => wgpu::TextureFormat::Rg8Unorm,
54 AtlasImageFormat::R8G8B8 => wgpu::TextureFormat::Rgba8Unorm, AtlasImageFormat::R8G8B8A8 => wgpu::TextureFormat::Rgba8Unorm,
56 AtlasImageFormat::R16 => wgpu::TextureFormat::R16Unorm,
57 AtlasImageFormat::R16G16 => wgpu::TextureFormat::Rg16Unorm,
58 AtlasImageFormat::R16G16B16 => wgpu::TextureFormat::Rgba16Unorm, AtlasImageFormat::R16G16B16A16 => wgpu::TextureFormat::Rgba16Unorm,
60 AtlasImageFormat::R16G16B16A16FLOAT => wgpu::TextureFormat::Rgba16Float,
61 AtlasImageFormat::R32FLOAT => wgpu::TextureFormat::R32Float,
62 AtlasImageFormat::R32G32B32FLOAT => wgpu::TextureFormat::Rgba32Float, AtlasImageFormat::R32G32B32A32FLOAT => wgpu::TextureFormat::Rgba32Float,
64 AtlasImageFormat::D32FLOAT => wgpu::TextureFormat::Depth32Float,
65 }
66 }
67}
68
69impl AtlasImageFormat {
70 pub fn from_wgpu_texture_format(value: wgpu::TextureFormat) -> Option<Self> {
71 match value {
72 wgpu::TextureFormat::R8Uint => Some(AtlasImageFormat::R8),
73 wgpu::TextureFormat::R16Uint => Some(AtlasImageFormat::R16),
74 wgpu::TextureFormat::R32Float => Some(AtlasImageFormat::R32FLOAT),
75 wgpu::TextureFormat::Rg8Uint => Some(AtlasImageFormat::R8G8),
76 wgpu::TextureFormat::Rg16Uint => Some(AtlasImageFormat::R16G16),
77 wgpu::TextureFormat::Rgba16Float => Some(AtlasImageFormat::R16G16B16A16FLOAT),
78 wgpu::TextureFormat::Depth32Float => Some(AtlasImageFormat::D32FLOAT),
79 _ => None,
80 }
81 }
82
83 pub fn zero_pixel(&self) -> &[u8] {
84 match self {
85 AtlasImageFormat::R8 => &[0],
86 AtlasImageFormat::R8G8 => &[0, 0],
87 AtlasImageFormat::R8G8B8 => &[0, 0, 0],
88 AtlasImageFormat::R8G8B8A8 => &[0, 0, 0, 0],
89 AtlasImageFormat::R16 => &[0, 0],
90 AtlasImageFormat::R16G16 => &[0, 0, 0, 0],
91 AtlasImageFormat::R16G16B16 => &[0, 0, 0, 0, 0, 0],
92 AtlasImageFormat::R16G16B16A16 => &[0, 0, 0, 0, 0, 0, 0, 0],
93 AtlasImageFormat::R16G16B16A16FLOAT => &[0, 0, 0, 0, 0, 0, 0, 0],
94 AtlasImageFormat::R32FLOAT | AtlasImageFormat::D32FLOAT => &[0, 0, 0, 0],
95 AtlasImageFormat::R32G32B32FLOAT => &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
96 AtlasImageFormat::R32G32B32A32FLOAT => {
97 &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
98 }
99 }
100 }
101}
102
103#[derive(Clone, Debug)]
105pub struct AtlasImage {
106 pub pixels: Vec<u8>,
107 pub size: UVec2,
108 pub format: AtlasImageFormat,
109 pub apply_linear_transfer: bool,
111}
112
113#[cfg(feature = "gltf")]
114impl From<gltf::image::Data> for AtlasImage {
115 fn from(value: gltf::image::Data) -> Self {
116 let pixels = value.pixels;
117 let size = UVec2::new(value.width, value.height);
118 let format = match value.format {
119 gltf::image::Format::R8 => AtlasImageFormat::R8,
120 gltf::image::Format::R8G8 => AtlasImageFormat::R8G8,
121 gltf::image::Format::R8G8B8 => AtlasImageFormat::R8G8B8,
122 gltf::image::Format::R8G8B8A8 => AtlasImageFormat::R8G8B8A8,
123 gltf::image::Format::R16 => AtlasImageFormat::R16,
124 gltf::image::Format::R16G16 => AtlasImageFormat::R16G16,
125 gltf::image::Format::R16G16B16 => AtlasImageFormat::R16G16B16,
126 gltf::image::Format::R16G16B16A16 => AtlasImageFormat::R16G16B16A16,
127 gltf::image::Format::R32G32B32FLOAT => AtlasImageFormat::R32G32B32FLOAT,
128 gltf::image::Format::R32G32B32A32FLOAT => AtlasImageFormat::R32G32B32A32FLOAT,
129 };
130
131 AtlasImage {
132 size,
133 pixels,
134 format,
135 apply_linear_transfer: false,
137 }
138 }
139}
140
141impl From<image::DynamicImage> for AtlasImage {
142 fn from(value: image::DynamicImage) -> Self {
143 let width = value.width();
144 let height = value.height();
145
146 use AtlasImageFormat::*;
147 let (pixels, format) = match value {
148 image::DynamicImage::ImageLuma8(img) => (img.into_vec(), R8),
149 i @ image::DynamicImage::ImageLumaA8(_) => (i.into_rgba8().into_vec(), R8G8B8A8),
150 image::DynamicImage::ImageRgb8(img) => (img.into_vec(), R8G8B8),
151 image::DynamicImage::ImageRgba8(img) => (img.into_vec(), R8G8B8A8),
152 image::DynamicImage::ImageLuma16(img) => (img.as_bytes().to_vec(), R16),
153 i @ image::DynamicImage::ImageLumaA16(_) => {
154 (i.into_rgba16().as_bytes().to_vec(), R16G16B16A16)
155 }
156 i @ image::DynamicImage::ImageRgb16(_) => (i.as_bytes().to_vec(), R16G16B16),
157 i @ image::DynamicImage::ImageRgba16(_) => (i.as_bytes().to_vec(), R16G16B16A16),
158 i @ image::DynamicImage::ImageRgb32F(_) => (i.as_bytes().to_vec(), R32G32B32FLOAT),
159 i @ image::DynamicImage::ImageRgba32F(_) => (i.as_bytes().to_vec(), R32G32B32A32FLOAT),
160 _ => todo!(),
161 };
162 AtlasImage {
163 pixels,
164 format,
165 apply_linear_transfer: true,
168 size: UVec2::new(width, height),
169 }
170 }
171}
172
173impl TryFrom<std::path::PathBuf> for AtlasImage {
174 type Error = AtlasImageError;
175
176 fn try_from(value: std::path::PathBuf) -> Result<Self, Self::Error> {
177 let img = image::open(value).context(ImageSnafu)?;
178 Ok(img.into())
179 }
180}
181
182impl AtlasImage {
183 pub fn from_hdr_path(p: impl AsRef<std::path::Path>) -> Result<Self, AtlasImageError> {
184 let bytes = std::fs::read(p.as_ref()).with_context(|_| CannotLoadSnafu {
185 path: std::path::PathBuf::from(p.as_ref()),
186 })?;
187 Self::from_hdr_bytes(&bytes)
188 }
189
190 pub fn from_hdr_bytes(bytes: &[u8]) -> Result<Self, AtlasImageError> {
191 let decoder = image::codecs::hdr::HdrDecoder::new(bytes).context(ImageSnafu)?;
193 let width = decoder.metadata().width;
194 let height = decoder.metadata().height;
195 let img = image::DynamicImage::from_decoder(decoder).unwrap();
196 let pixels = img.into_rgb32f();
197
198 let mut pixel_data: Vec<f32> = Vec::new();
200 for pixel in pixels.pixels() {
201 pixel_data.push(pixel[0]);
202 pixel_data.push(pixel[1]);
203 pixel_data.push(pixel[2]);
204 pixel_data.push(1.0);
205 }
206 let mut pixels = vec![];
207 pixels.extend_from_slice(bytemuck::cast_slice(pixel_data.as_slice()));
208
209 Ok(Self {
210 pixels,
211 size: UVec2::new(width, height),
212 format: AtlasImageFormat::R32G32B32A32FLOAT,
213 apply_linear_transfer: false,
214 })
215 }
216
217 pub fn from_path(p: impl AsRef<std::path::Path>) -> Result<Self, AtlasImageError> {
218 Self::try_from(p.as_ref().to_path_buf())
219 }
220
221 pub fn into_rgba8(self) -> Option<image::RgbaImage> {
222 let pixels = convert_pixels(
223 self.pixels,
224 self.format,
225 wgpu::TextureFormat::Rgba8Unorm,
226 self.apply_linear_transfer,
227 );
228 image::RgbaImage::from_vec(self.size.x, self.size.y, pixels)
229 }
230
231 pub fn new(size: UVec2, format: AtlasImageFormat) -> Self {
233 Self {
234 pixels: std::iter::repeat_n(format.zero_pixel(), (size.x * size.y) as usize)
235 .flatten()
236 .copied()
237 .collect(),
238 size,
239 format,
240 apply_linear_transfer: false,
241 }
242 }
243}
244
245fn apply_linear_xfer(bytes: &mut [u8], format: AtlasImageFormat) {
246 use crate::color::*;
247 match format {
248 AtlasImageFormat::R8
249 | AtlasImageFormat::R8G8
250 | AtlasImageFormat::R8G8B8
251 | AtlasImageFormat::R8G8B8A8 => {
252 bytes.iter_mut().for_each(linear_xfer_u8);
253 }
254 AtlasImageFormat::R16
255 | AtlasImageFormat::R16G16
256 | AtlasImageFormat::R16G16B16
257 | AtlasImageFormat::R16G16B16A16 => {
258 let bytes: &mut [u16] = bytemuck::cast_slice_mut(bytes);
259 bytes.iter_mut().for_each(linear_xfer_u16);
260 }
261 AtlasImageFormat::R16G16B16A16FLOAT => {
262 let bytes: &mut [u16] = bytemuck::cast_slice_mut(bytes);
263 bytes.iter_mut().for_each(linear_xfer_f16);
264 }
265 AtlasImageFormat::R32G32B32FLOAT
266 | AtlasImageFormat::R32G32B32A32FLOAT
267 | AtlasImageFormat::D32FLOAT
268 | AtlasImageFormat::R32FLOAT => {
269 let bytes: &mut [f32] = bytemuck::cast_slice_mut(bytes);
270 bytes.iter_mut().for_each(linear_xfer_f32);
271 }
272 }
273}
274
275pub fn convert_pixels(
281 bytes: impl IntoIterator<Item = u8>,
282 from_format: AtlasImageFormat,
283 to_format: wgpu::TextureFormat,
284 apply_linear_transfer: bool,
285) -> Vec<u8> {
286 use crate::color::*;
287 let mut bytes = bytes.into_iter().collect::<Vec<_>>();
288 log::trace!("converting image of format {from_format:?}");
289 if apply_linear_transfer {
291 log::trace!(" converting to linear color space (from sRGB)");
292 apply_linear_xfer(&mut bytes, from_format);
293 }
294
295 match (from_format, to_format) {
297 (AtlasImageFormat::R8, wgpu::TextureFormat::Rgba8Unorm) => {
298 bytes.into_iter().flat_map(|r| [r, 0, 0, 255]).collect()
299 }
300 (AtlasImageFormat::R8G8, wgpu::TextureFormat::Rgba8Unorm) => bytes
301 .chunks_exact(2)
302 .flat_map(|p| {
303 if let [r, g] = p {
304 [*r, *g, 0, 255]
305 } else {
306 unreachable!()
307 }
308 })
309 .collect(),
310 (AtlasImageFormat::R8G8B8, wgpu::TextureFormat::Rgba8Unorm) => bytes
311 .chunks_exact(3)
312 .flat_map(|p| {
313 if let [r, g, b] = p {
314 [*r, *g, *b, 255]
315 } else {
316 unreachable!()
317 }
318 })
319 .collect(),
320 (AtlasImageFormat::R8G8B8A8, wgpu::TextureFormat::Rgba8Unorm) => bytes,
321 (AtlasImageFormat::R16, wgpu::TextureFormat::Rgba8Unorm) => {
322 bytemuck::cast_slice::<u8, u16>(&bytes)
323 .iter()
324 .flat_map(|r| [u16_to_u8(*r), 0, 0, 255])
325 .collect()
326 }
327 (AtlasImageFormat::R16G16, wgpu::TextureFormat::Rgba8Unorm) => {
328 bytemuck::cast_slice::<u8, u16>(&bytes)
329 .chunks_exact(2)
330 .flat_map(|p| {
331 if let [r, g] = p {
332 [u16_to_u8(*r), u16_to_u8(*g), 0, 255]
333 } else {
334 unreachable!()
335 }
336 })
337 .collect()
338 }
339 (AtlasImageFormat::R16G16B16, wgpu::TextureFormat::Rgba8Unorm) => {
340 bytemuck::cast_slice::<u8, u16>(&bytes)
341 .chunks_exact(3)
342 .flat_map(|p| {
343 if let [r, g, b] = p {
344 [u16_to_u8(*r), u16_to_u8(*g), u16_to_u8(*b), 255]
345 } else {
346 unreachable!()
347 }
348 })
349 .collect()
350 }
351
352 (AtlasImageFormat::R16G16B16A16, wgpu::TextureFormat::Rgba8Unorm) => {
353 bytemuck::cast_slice::<u8, u16>(&bytes)
354 .iter()
355 .copied()
356 .map(u16_to_u8)
357 .collect()
358 }
359 (AtlasImageFormat::R16G16B16A16FLOAT, wgpu::TextureFormat::Rgba8Unorm) => {
360 bytemuck::cast_slice::<u8, u16>(&bytes)
361 .iter()
362 .map(|bits| half::f16::from_bits(*bits).to_f32())
363 .collect::<Vec<_>>()
364 .chunks_exact(4)
365 .flat_map(|p| {
366 if let [r, g, b, a] = p {
367 [f32_to_u8(*r), f32_to_u8(*g), f32_to_u8(*b), f32_to_u8(*a)]
368 } else {
369 unreachable!()
370 }
371 })
372 .collect()
373 }
374 (AtlasImageFormat::R32G32B32FLOAT, wgpu::TextureFormat::Rgba8Unorm) => {
375 bytemuck::cast_slice::<u8, f32>(&bytes)
376 .chunks_exact(3)
377 .flat_map(|p| {
378 if let [r, g, b] = p {
379 [f32_to_u8(*r), f32_to_u8(*g), f32_to_u8(*b), 255]
380 } else {
381 unreachable!()
382 }
383 })
384 .collect()
385 }
386 (AtlasImageFormat::R32G32B32A32FLOAT, wgpu::TextureFormat::Rgba8Unorm)
387 | (AtlasImageFormat::R32FLOAT, wgpu::TextureFormat::Rgba8Unorm)
388 | (AtlasImageFormat::D32FLOAT, wgpu::TextureFormat::Rgba8Unorm) => {
389 bytemuck::cast_slice::<u8, f32>(&bytes)
390 .iter()
391 .copied()
392 .map(f32_to_u8)
393 .collect()
394 }
395 (AtlasImageFormat::R32FLOAT, wgpu::TextureFormat::R32Float) => bytes,
396 (from, to) => panic!("cannot convert from atlas format {from:?} to {to:?}"),
398 }
399}