mod bcn;
mod rgba;
mod surface;
use rgba::convert::Channel;
pub use surface::{Surface, SurfaceRgba32Float, SurfaceRgba8};
pub mod error;
use error::*;
#[cfg(feature = "ddsfile")]
pub use ddsfile;
#[cfg(feature = "image")]
pub use image;
mod decode;
#[cfg(feature = "encode")]
mod encode;
#[cfg(feature = "ddsfile")]
mod dds;
#[cfg(feature = "ddsfile")]
pub use dds::*;
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "strum",
derive(strum::EnumString, strum::Display, strum::EnumIter)
)]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Quality {
Fast,
Normal,
Slow,
}
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "strum",
derive(strum::EnumString, strum::Display, strum::EnumIter)
)]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Mipmaps {
Disabled,
FromSurface,
GeneratedExact(u32),
GeneratedAutomatic,
}
#[non_exhaustive]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "strum",
derive(strum::EnumString, strum::Display, strum::EnumIter)
)]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum ImageFormat {
R8Unorm,
R8Snorm,
Rg8Unorm,
Rg8Snorm,
Rgba8Unorm,
Rgba8UnormSrgb,
Rgba16Float,
Rgba32Float,
Bgr8Unorm,
Bgra8Unorm,
Bgra8UnormSrgb,
Bgra4Unorm,
BC1RgbaUnorm,
BC1RgbaUnormSrgb,
BC2RgbaUnorm,
BC2RgbaUnormSrgb,
BC3RgbaUnorm,
BC3RgbaUnormSrgb,
BC4RUnorm,
BC4RSnorm,
BC5RgUnorm,
BC5RgSnorm,
BC6hRgbUfloat,
BC6hRgbSfloat,
BC7RgbaUnorm,
BC7RgbaUnormSrgb,
Rgba8Snorm,
R16Unorm,
R16Snorm,
Rg16Unorm,
Rg16Snorm,
Rgba16Unorm,
Rgba16Snorm,
R16Float,
Rg16Float,
R32Float,
Rg32Float,
Rgb32Float,
Bgr5A1Unorm,
}
impl ImageFormat {
fn block_dimensions(&self) -> (u32, u32, u32) {
match self {
ImageFormat::BC1RgbaUnorm => (4, 4, 1),
ImageFormat::BC1RgbaUnormSrgb => (4, 4, 1),
ImageFormat::BC2RgbaUnorm => (4, 4, 1),
ImageFormat::BC2RgbaUnormSrgb => (4, 4, 1),
ImageFormat::BC3RgbaUnorm => (4, 4, 1),
ImageFormat::BC3RgbaUnormSrgb => (4, 4, 1),
ImageFormat::BC4RUnorm => (4, 4, 1),
ImageFormat::BC4RSnorm => (4, 4, 1),
ImageFormat::BC5RgUnorm => (4, 4, 1),
ImageFormat::BC5RgSnorm => (4, 4, 1),
ImageFormat::BC6hRgbUfloat => (4, 4, 1),
ImageFormat::BC6hRgbSfloat => (4, 4, 1),
ImageFormat::BC7RgbaUnorm => (4, 4, 1),
ImageFormat::BC7RgbaUnormSrgb => (4, 4, 1),
_ => (1, 1, 1),
}
}
fn block_size_in_bytes(&self) -> usize {
match self {
ImageFormat::R8Unorm => 1,
ImageFormat::R8Snorm => 1,
ImageFormat::Rg8Unorm => 2,
ImageFormat::Rg8Snorm => 2,
ImageFormat::Rgba8Unorm => 4,
ImageFormat::Rgba8UnormSrgb => 4,
ImageFormat::Rgba16Float => 8,
ImageFormat::Rgba32Float => 16,
ImageFormat::Bgra8Unorm => 4,
ImageFormat::Bgra8UnormSrgb => 4,
ImageFormat::BC1RgbaUnorm => 8,
ImageFormat::BC1RgbaUnormSrgb => 8,
ImageFormat::BC2RgbaUnorm => 16,
ImageFormat::BC2RgbaUnormSrgb => 16,
ImageFormat::BC3RgbaUnorm => 16,
ImageFormat::BC3RgbaUnormSrgb => 16,
ImageFormat::BC4RUnorm => 8,
ImageFormat::BC4RSnorm => 8,
ImageFormat::BC5RgUnorm => 16,
ImageFormat::BC5RgSnorm => 16,
ImageFormat::BC6hRgbUfloat => 16,
ImageFormat::BC6hRgbSfloat => 16,
ImageFormat::BC7RgbaUnorm => 16,
ImageFormat::BC7RgbaUnormSrgb => 16,
ImageFormat::Bgra4Unorm => 2,
ImageFormat::Bgr8Unorm => 3,
ImageFormat::R16Unorm => 2,
ImageFormat::R16Snorm => 2,
ImageFormat::Rg16Unorm => 4,
ImageFormat::Rg16Snorm => 4,
ImageFormat::Rgba16Unorm => 8,
ImageFormat::Rgba16Snorm => 8,
ImageFormat::Rg16Float => 4,
ImageFormat::Rg32Float => 8,
ImageFormat::R16Float => 2,
ImageFormat::R32Float => 4,
ImageFormat::Rgba8Snorm => 4,
ImageFormat::Rgb32Float => 12,
ImageFormat::Bgr5A1Unorm => 2,
}
}
}
fn max_mipmap_count(max_dimension: u32) -> u32 {
u32::BITS - max_dimension.leading_zeros()
}
pub fn mip_dimension(base_dimension: u32, mipmap: u32) -> u32 {
(base_dimension >> mipmap).max(1)
}
fn downsample_rgba<T: Channel>(
new_width: usize,
new_height: usize,
new_depth: usize,
width: usize,
height: usize,
depth: usize,
data: &[T],
) -> Vec<T> {
let mut new_data = vec![T::ZERO; new_width * new_height * new_depth * 4];
for z in 0..new_depth {
for x in 0..new_width {
for y in 0..new_height {
let new_index = (z * new_width * new_height) + y * new_width + x;
for c in 0..4 {
let mut sum = 0.0;
let mut count = 0u64;
for z2 in 0..2 {
let sampled_z = (z * 2) + z2;
if sampled_z < depth {
for y2 in 0..2 {
let sampled_y = (y * 2) + y2;
if sampled_y < height {
for x2 in 0..2 {
let sampled_x = (x * 2) + x2;
if sampled_x < width {
let index = (sampled_z * width * height)
+ (sampled_y * width)
+ sampled_x;
sum += data[index * 4 + c].to_f32();
count += 1;
}
}
}
}
}
}
new_data[new_index * 4 + c] = T::from_f32(sum / count.max(1) as f32);
}
}
}
}
new_data
}
fn calculate_offset(
layer: u32,
depth_level: u32,
mipmap: u32,
dimensions: (u32, u32, u32),
block_dimensions: (u32, u32, u32),
block_size_in_bytes: usize,
mipmaps_per_layer: u32,
) -> Option<usize> {
let (width, height, depth) = dimensions;
let (block_width, block_height, block_depth) = block_dimensions;
let mip_sizes = (0..mipmaps_per_layer)
.map(|i| {
let mip_width = mip_dimension(width, i) as usize;
let mip_height = mip_dimension(height, i) as usize;
let mip_depth = mip_dimension(depth, i) as usize;
mip_size(
mip_width,
mip_height,
mip_depth,
block_width as usize,
block_height as usize,
block_depth as usize,
block_size_in_bytes,
)
})
.collect::<Option<Vec<_>>>()?;
let mip_width = mip_dimension(width, mipmap) as usize;
let mip_height = mip_dimension(height, mipmap) as usize;
let mip_size2d = mip_size(
mip_width,
mip_height,
1,
block_width as usize,
block_height as usize,
block_depth as usize,
block_size_in_bytes,
)?;
let layer_size: usize = mip_sizes.iter().sum();
let layer_offset = layer as usize * layer_size;
let mip_offset: usize = mip_sizes.get(0..mipmap as usize)?.iter().sum();
let depth_offset = mip_size2d * depth_level as usize;
Some(layer_offset + mip_offset + depth_offset)
}
fn mip_size(
width: usize,
height: usize,
depth: usize,
block_width: usize,
block_height: usize,
block_depth: usize,
block_size_in_bytes: usize,
) -> Option<usize> {
width
.div_ceil(block_width)
.checked_mul(height.div_ceil(block_height))
.and_then(|v| v.checked_mul(depth.div_ceil(block_depth)))
.and_then(|v| v.checked_mul(block_size_in_bytes))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn max_mipmap_count_zero() {
assert_eq!(0, max_mipmap_count(0));
}
#[test]
fn max_mipmap_count_1() {
assert_eq!(1, max_mipmap_count(1));
}
#[test]
fn max_mipmap_count_4() {
assert_eq!(4, max_mipmap_count(12));
}
#[test]
fn downsample_rgba8_4x4() {
let original: Vec<_> = std::iter::repeat([0u8, 0u8, 0u8, 0u8, 255u8, 255u8, 255u8, 255u8])
.take(4 * 4 / 2)
.flatten()
.collect();
assert_eq!(
vec![127u8; 2 * 2 * 1 * 4],
downsample_rgba(2, 2, 1, 4, 4, 1, &original)
);
}
#[test]
fn downsample_rgba8_3x3() {
let original: Vec<_> = std::iter::repeat([
0u8, 0u8, 0u8, 0u8, 255u8, 255u8, 255u8, 255u8, 0u8, 0u8, 0u8, 0u8,
])
.take(3 * 3 / 3)
.flatten()
.collect();
assert_eq!(
vec![127u8; 1 * 1 * 4],
downsample_rgba(1, 1, 1, 3, 3, 1, &original)
);
}
#[test]
fn downsample_rgba8_2x2x2() {
let original = vec![
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
];
assert_eq!(
vec![127u8; 1 * 1 * 1 * 4],
downsample_rgba(1, 1, 1, 2, 2, 2, &original)
);
}
#[test]
fn downsample_rgba8_0x0() {
assert_eq!(vec![0u8; 4], downsample_rgba(1, 1, 1, 0, 0, 1, &[]));
}
#[test]
fn downsample_rgbaf32_4x4() {
let original: Vec<_> = std::iter::repeat([
0.0f32, 0.0f32, 0.0f32, 0.0f32, 1.0f32, 1.0f32, 1.0f32, 1.0f32,
])
.take(4 * 4 / 2)
.flatten()
.collect();
assert_eq!(
vec![0.5; 2 * 2 * 1 * 4],
downsample_rgba(2, 2, 1, 4, 4, 1, &original)
);
}
#[test]
fn downsample_rgbaf32_3x3() {
let original: Vec<_> = std::iter::repeat([
0.0f32, 0.0f32, 0.0f32, 0.0f32, 1.0f32, 1.0f32, 1.0f32, 1.0f32, 0.0f32, 0.0f32, 0.0f32,
0.0f32,
])
.take(3 * 3 / 3)
.flatten()
.collect();
assert_eq!(
vec![0.5; 1 * 1 * 4],
downsample_rgba(1, 1, 1, 3, 3, 1, &original)
);
}
#[test]
fn downsample_rgbaf32_2x2x2() {
let original = vec![
0.0f32, 0.0f32, 0.0f32, 0.0f32, 0.0f32, 0.0f32, 0.0f32, 0.0f32, 0.0f32, 0.0f32, 0.0f32,
0.0f32, 0.0f32, 0.0f32, 0.0f32, 0.0f32, 1.0f32, 1.0f32, 1.0f32, 1.0f32, 1.0f32, 1.0f32,
1.0f32, 1.0f32, 1.0f32, 1.0f32, 1.0f32, 1.0f32, 1.0f32, 1.0f32, 1.0f32, 1.0f32,
];
assert_eq!(
vec![0.5; 1 * 1 * 1 * 4],
downsample_rgba(1, 1, 1, 2, 2, 2, &original)
);
}
#[test]
fn downsample_rgbaf32_0x0() {
assert_eq!(vec![0.0f32; 4], downsample_rgba(1, 1, 1, 0, 0, 1, &[]));
}
#[test]
fn calculate_offset_layer0_mip0() {
assert_eq!(
0,
calculate_offset(0, 0, 0, (8, 8, 8), (4, 4, 4), 16, 4).unwrap()
);
}
#[test]
fn calculate_offset_layer0_mip2() {
assert_eq!(
128 + 16,
calculate_offset(0, 0, 2, (8, 8, 8), (4, 4, 4), 16, 4).unwrap()
);
}
#[test]
fn calculate_offset_layer2_mip0() {
assert_eq!(
(128 + 16 + 16 + 16) * 2,
calculate_offset(2, 0, 0, (8, 8, 8), (4, 4, 4), 16, 4).unwrap()
);
}
#[test]
fn calculate_offset_layer2_mip2() {
assert_eq!(
(128 + 16 + 16 + 16) * 2 + 128 + 16,
calculate_offset(2, 0, 2, (8, 8, 8), (4, 4, 4), 16, 4).unwrap()
);
}
#[test]
fn calculate_offset_level2() {
assert_eq!(
16 * 16 * 2,
calculate_offset(0, 2, 0, (15, 15, 15), (4, 4, 4), 16, 1).unwrap()
);
}
#[test]
fn calculate_offset_level3() {
assert_eq!(
16 * 16 * 3 * 4,
calculate_offset(0, 3, 0, (16, 16, 16), (1, 1, 1), 4, 1).unwrap()
);
}
}