1mod bcn;
82mod rgba;
83mod surface;
84
85use rgba::convert::Channel;
86pub use surface::{Surface, SurfaceRgba32Float, SurfaceRgba8};
87
88pub mod error;
89use error::*;
90
91#[cfg(feature = "ddsfile")]
92pub use ddsfile;
93
94#[cfg(feature = "image")]
95pub use image;
96
97mod decode;
98
99#[cfg(feature = "encode")]
100mod encode;
101
102#[cfg(feature = "ddsfile")]
103mod dds;
104#[cfg(feature = "ddsfile")]
105pub use dds::*;
106
107#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
113#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
114#[cfg_attr(
115 feature = "strum",
116 derive(strum::EnumString, strum::Display, strum::EnumIter)
117)]
118#[derive(Debug, PartialEq, Eq, Clone, Copy)]
119pub enum Quality {
120 Fast,
122 Normal,
124 Slow,
126}
127
128#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
132#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
133#[cfg_attr(
134 feature = "strum",
135 derive(strum::EnumString, strum::Display, strum::EnumIter)
136)]
137#[derive(Debug, PartialEq, Eq, Clone, Copy)]
138pub enum Mipmaps {
139 Disabled,
141 FromSurface,
143 GeneratedExact(u32),
146 GeneratedAutomatic,
149}
150
151#[non_exhaustive]
156#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
157#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
158#[cfg_attr(
159 feature = "strum",
160 derive(strum::EnumString, strum::Display, strum::EnumIter)
161)]
162#[derive(Debug, PartialEq, Eq, Clone, Copy)]
163pub enum ImageFormat {
164 R8Unorm,
165 R8Snorm,
166 Rg8Unorm,
167 Rg8Snorm,
168 Rgba8Unorm,
169 Rgba8UnormSrgb,
170 Rgba16Float,
171 Rgba32Float,
172 Bgr8Unorm,
173 Bgra8Unorm,
174 Bgra8UnormSrgb,
175 Bgra4Unorm,
176 BC1RgbaUnorm,
178 BC1RgbaUnormSrgb,
179 BC2RgbaUnorm,
181 BC2RgbaUnormSrgb,
182 BC3RgbaUnorm,
184 BC3RgbaUnormSrgb,
185 BC4RUnorm,
187 BC4RSnorm,
188 BC5RgUnorm,
190 BC5RgSnorm,
191 BC6hRgbUfloat,
193 BC6hRgbSfloat,
194 BC7RgbaUnorm,
196 BC7RgbaUnormSrgb,
197 Rgba8Snorm,
198 R16Unorm,
199 R16Snorm,
200 Rg16Unorm,
201 Rg16Snorm,
202 Rgba16Unorm,
203 Rgba16Snorm,
204 R16Float,
205 Rg16Float,
206 R32Float,
207 Rg32Float,
208 Rgb32Float,
209 Bgr5A1Unorm,
210}
211
212impl ImageFormat {
213 fn block_dimensions(&self) -> (u32, u32, u32) {
215 match self {
216 ImageFormat::BC1RgbaUnorm => (4, 4, 1),
217 ImageFormat::BC1RgbaUnormSrgb => (4, 4, 1),
218 ImageFormat::BC2RgbaUnorm => (4, 4, 1),
219 ImageFormat::BC2RgbaUnormSrgb => (4, 4, 1),
220 ImageFormat::BC3RgbaUnorm => (4, 4, 1),
221 ImageFormat::BC3RgbaUnormSrgb => (4, 4, 1),
222 ImageFormat::BC4RUnorm => (4, 4, 1),
223 ImageFormat::BC4RSnorm => (4, 4, 1),
224 ImageFormat::BC5RgUnorm => (4, 4, 1),
225 ImageFormat::BC5RgSnorm => (4, 4, 1),
226 ImageFormat::BC6hRgbUfloat => (4, 4, 1),
227 ImageFormat::BC6hRgbSfloat => (4, 4, 1),
228 ImageFormat::BC7RgbaUnorm => (4, 4, 1),
229 ImageFormat::BC7RgbaUnormSrgb => (4, 4, 1),
230 _ => (1, 1, 1),
231 }
232 }
233
234 fn block_size_in_bytes(&self) -> usize {
235 match self {
237 ImageFormat::R8Unorm => 1,
238 ImageFormat::R8Snorm => 1,
239 ImageFormat::Rg8Unorm => 2,
240 ImageFormat::Rg8Snorm => 2,
241 ImageFormat::Rgba8Unorm => 4,
242 ImageFormat::Rgba8UnormSrgb => 4,
243 ImageFormat::Rgba16Float => 8,
244 ImageFormat::Rgba32Float => 16,
245 ImageFormat::Bgra8Unorm => 4,
246 ImageFormat::Bgra8UnormSrgb => 4,
247 ImageFormat::BC1RgbaUnorm => 8,
248 ImageFormat::BC1RgbaUnormSrgb => 8,
249 ImageFormat::BC2RgbaUnorm => 16,
250 ImageFormat::BC2RgbaUnormSrgb => 16,
251 ImageFormat::BC3RgbaUnorm => 16,
252 ImageFormat::BC3RgbaUnormSrgb => 16,
253 ImageFormat::BC4RUnorm => 8,
254 ImageFormat::BC4RSnorm => 8,
255 ImageFormat::BC5RgUnorm => 16,
256 ImageFormat::BC5RgSnorm => 16,
257 ImageFormat::BC6hRgbUfloat => 16,
258 ImageFormat::BC6hRgbSfloat => 16,
259 ImageFormat::BC7RgbaUnorm => 16,
260 ImageFormat::BC7RgbaUnormSrgb => 16,
261 ImageFormat::Bgra4Unorm => 2,
262 ImageFormat::Bgr8Unorm => 3,
263 ImageFormat::R16Unorm => 2,
264 ImageFormat::R16Snorm => 2,
265 ImageFormat::Rg16Unorm => 4,
266 ImageFormat::Rg16Snorm => 4,
267 ImageFormat::Rgba16Unorm => 8,
268 ImageFormat::Rgba16Snorm => 8,
269 ImageFormat::Rg16Float => 4,
270 ImageFormat::Rg32Float => 8,
271 ImageFormat::R16Float => 2,
272 ImageFormat::R32Float => 4,
273 ImageFormat::Rgba8Snorm => 4,
274 ImageFormat::Rgb32Float => 12,
275 ImageFormat::Bgr5A1Unorm => 2,
276 }
277 }
278}
279
280fn max_mipmap_count(max_dimension: u32) -> u32 {
281 u32::BITS - max_dimension.leading_zeros()
283}
284
285pub fn mip_dimension(base_dimension: u32, mipmap: u32) -> u32 {
287 (base_dimension >> mipmap).max(1)
289}
290
291fn downsample_rgba<T: Channel>(
292 new_width: usize,
293 new_height: usize,
294 new_depth: usize,
295 width: usize,
296 height: usize,
297 depth: usize,
298 data: &[T],
299) -> Vec<T> {
300 let mut new_data = vec![T::ZERO; new_width * new_height * new_depth * 4];
303 for z in 0..new_depth {
304 for x in 0..new_width {
305 for y in 0..new_height {
306 let new_index = (z * new_width * new_height) + y * new_width + x;
307
308 for c in 0..4 {
311 let mut sum = 0.0;
312 let mut count = 0u64;
313 for z2 in 0..2 {
314 let sampled_z = (z * 2) + z2;
315 if sampled_z < depth {
316 for y2 in 0..2 {
317 let sampled_y = (y * 2) + y2;
318 if sampled_y < height {
319 for x2 in 0..2 {
320 let sampled_x = (x * 2) + x2;
321 if sampled_x < width {
322 let index = (sampled_z * width * height)
323 + (sampled_y * width)
324 + sampled_x;
325 sum += data[index * 4 + c].to_f32();
326 count += 1;
327 }
328 }
329 }
330 }
331 }
332 }
333 new_data[new_index * 4 + c] = T::from_f32(sum / count.max(1) as f32);
334 }
335 }
336 }
337 }
338
339 new_data
340}
341
342fn calculate_offset(
343 layer: u32,
344 depth_level: u32,
345 mipmap: u32,
346 dimensions: (u32, u32, u32),
347 block_dimensions: (u32, u32, u32),
348 block_size_in_bytes: usize,
349 mipmaps_per_layer: u32,
350) -> Option<usize> {
351 let (width, height, depth) = dimensions;
354 let (block_width, block_height, block_depth) = block_dimensions;
355
356 let mip_sizes = (0..mipmaps_per_layer)
357 .map(|i| {
358 let mip_width = mip_dimension(width, i) as usize;
359 let mip_height = mip_dimension(height, i) as usize;
360 let mip_depth = mip_dimension(depth, i) as usize;
361
362 mip_size(
363 mip_width,
364 mip_height,
365 mip_depth,
366 block_width as usize,
367 block_height as usize,
368 block_depth as usize,
369 block_size_in_bytes,
370 )
371 })
372 .collect::<Option<Vec<_>>>()?;
373
374 let mip_width = mip_dimension(width, mipmap) as usize;
376 let mip_height = mip_dimension(height, mipmap) as usize;
377 let mip_size2d = mip_size(
378 mip_width,
379 mip_height,
380 1,
381 block_width as usize,
382 block_height as usize,
383 block_depth as usize,
384 block_size_in_bytes,
385 )?;
386
387 let layer_size: usize = mip_sizes.iter().sum();
390
391 let layer_offset = layer as usize * layer_size;
393 let mip_offset: usize = mip_sizes.get(0..mipmap as usize)?.iter().sum();
394 let depth_offset = mip_size2d * depth_level as usize;
395 Some(layer_offset + mip_offset + depth_offset)
396}
397
398fn mip_size(
399 width: usize,
400 height: usize,
401 depth: usize,
402 block_width: usize,
403 block_height: usize,
404 block_depth: usize,
405 block_size_in_bytes: usize,
406) -> Option<usize> {
407 width
408 .div_ceil(block_width)
409 .checked_mul(height.div_ceil(block_height))
410 .and_then(|v| v.checked_mul(depth.div_ceil(block_depth)))
411 .and_then(|v| v.checked_mul(block_size_in_bytes))
412}
413
414#[cfg(test)]
415mod tests {
416 use super::*;
417
418 #[test]
419 fn max_mipmap_count_zero() {
420 assert_eq!(0, max_mipmap_count(0));
421 }
422
423 #[test]
424 fn max_mipmap_count_1() {
425 assert_eq!(1, max_mipmap_count(1));
426 }
427
428 #[test]
429 fn max_mipmap_count_4() {
430 assert_eq!(4, max_mipmap_count(12));
431 }
432
433 #[test]
434 fn downsample_rgba8_4x4() {
435 let original: Vec<_> = std::iter::repeat([0u8, 0u8, 0u8, 0u8, 255u8, 255u8, 255u8, 255u8])
437 .take(4 * 4 / 2)
438 .flatten()
439 .collect();
440 assert_eq!(
441 vec![127u8; 2 * 2 * 1 * 4],
442 downsample_rgba(2, 2, 1, 4, 4, 1, &original)
443 );
444 }
445
446 #[test]
447 fn downsample_rgba8_3x3() {
448 let original: Vec<_> = std::iter::repeat([
450 0u8, 0u8, 0u8, 0u8, 255u8, 255u8, 255u8, 255u8, 0u8, 0u8, 0u8, 0u8,
451 ])
452 .take(3 * 3 / 3)
453 .flatten()
454 .collect();
455 assert_eq!(
456 vec![127u8; 1 * 1 * 4],
457 downsample_rgba(1, 1, 1, 3, 3, 1, &original)
458 );
459 }
460
461 #[test]
462 fn downsample_rgba8_2x2x2() {
463 let original = vec![
465 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255,
466 255, 255, 255, 255, 255, 255, 255, 255,
467 ];
468 assert_eq!(
469 vec![127u8; 1 * 1 * 1 * 4],
470 downsample_rgba(1, 1, 1, 2, 2, 2, &original)
471 );
472 }
473
474 #[test]
475 fn downsample_rgba8_0x0() {
476 assert_eq!(vec![0u8; 4], downsample_rgba(1, 1, 1, 0, 0, 1, &[]));
477 }
478
479 #[test]
480 fn downsample_rgbaf32_4x4() {
481 let original: Vec<_> = std::iter::repeat([
483 0.0f32, 0.0f32, 0.0f32, 0.0f32, 1.0f32, 1.0f32, 1.0f32, 1.0f32,
484 ])
485 .take(4 * 4 / 2)
486 .flatten()
487 .collect();
488 assert_eq!(
489 vec![0.5; 2 * 2 * 1 * 4],
490 downsample_rgba(2, 2, 1, 4, 4, 1, &original)
491 );
492 }
493
494 #[test]
495 fn downsample_rgbaf32_3x3() {
496 let original: Vec<_> = std::iter::repeat([
498 0.0f32, 0.0f32, 0.0f32, 0.0f32, 1.0f32, 1.0f32, 1.0f32, 1.0f32, 0.0f32, 0.0f32, 0.0f32,
499 0.0f32,
500 ])
501 .take(3 * 3 / 3)
502 .flatten()
503 .collect();
504 assert_eq!(
505 vec![0.5; 1 * 1 * 4],
506 downsample_rgba(1, 1, 1, 3, 3, 1, &original)
507 );
508 }
509
510 #[test]
511 fn downsample_rgbaf32_2x2x2() {
512 let original = vec![
514 0.0f32, 0.0f32, 0.0f32, 0.0f32, 0.0f32, 0.0f32, 0.0f32, 0.0f32, 0.0f32, 0.0f32, 0.0f32,
515 0.0f32, 0.0f32, 0.0f32, 0.0f32, 0.0f32, 1.0f32, 1.0f32, 1.0f32, 1.0f32, 1.0f32, 1.0f32,
516 1.0f32, 1.0f32, 1.0f32, 1.0f32, 1.0f32, 1.0f32, 1.0f32, 1.0f32, 1.0f32, 1.0f32,
517 ];
518 assert_eq!(
519 vec![0.5; 1 * 1 * 1 * 4],
520 downsample_rgba(1, 1, 1, 2, 2, 2, &original)
521 );
522 }
523
524 #[test]
525 fn downsample_rgbaf32_0x0() {
526 assert_eq!(vec![0.0f32; 4], downsample_rgba(1, 1, 1, 0, 0, 1, &[]));
527 }
528
529 #[test]
530 fn calculate_offset_layer0_mip0() {
531 assert_eq!(
532 0,
533 calculate_offset(0, 0, 0, (8, 8, 8), (4, 4, 4), 16, 4).unwrap()
534 );
535 }
536
537 #[test]
538 fn calculate_offset_layer0_mip2() {
539 assert_eq!(
541 128 + 16,
542 calculate_offset(0, 0, 2, (8, 8, 8), (4, 4, 4), 16, 4).unwrap()
543 );
544 }
545
546 #[test]
547 fn calculate_offset_layer2_mip0() {
548 assert_eq!(
551 (128 + 16 + 16 + 16) * 2,
552 calculate_offset(2, 0, 0, (8, 8, 8), (4, 4, 4), 16, 4).unwrap()
553 );
554 }
555
556 #[test]
557 fn calculate_offset_layer2_mip2() {
558 assert_eq!(
561 (128 + 16 + 16 + 16) * 2 + 128 + 16,
562 calculate_offset(2, 0, 2, (8, 8, 8), (4, 4, 4), 16, 4).unwrap()
563 );
564 }
565
566 #[test]
567 fn calculate_offset_level2() {
568 assert_eq!(
570 16 * 16 * 2,
571 calculate_offset(0, 2, 0, (15, 15, 15), (4, 4, 4), 16, 1).unwrap()
572 );
573 }
574
575 #[test]
576 fn calculate_offset_level3() {
577 assert_eq!(
579 16 * 16 * 3 * 4,
580 calculate_offset(0, 3, 0, (16, 16, 16), (1, 1, 1), 4, 1).unwrap()
581 );
582 }
583}