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