rusty_dds 0.1.2

Memory-safe DDS texture toolkit — container, decode, encode, GPU upload plans (Remade With Rust)
Documentation
//! CPU decode of DDS subresources to tightly packed RGBA8.
//!
//! **sRGB policy:** channel bytes are returned as stored. Formats tagged `_sRGB`
//! are **not** converted to linear — callers that need linear light must convert.
//!
//! Block compression (BC1–BC5, BC7) uses the pure-Rust [`bcdec_rs`] crate (MIT),
//! the same decompression core as `image_dds`. Uncompressed layouts are handled
//! in-house.

mod bcn;
mod uncompressed;
pub mod reference;

use crate::content::{slice_payload_bytes, DecodeContent, ImageRgba8};
use crate::error::Error;
use crate::surface::SubresourceId;
use crate::Dds;

impl Dds {
    /// Decode one subresource to tightly packed RGBA8.
    ///
    /// Volumes (`depth > 1`) decode every depth slice and stack them in
    /// [`ImageRgba8::pixels`]. sRGB-tagged formats return stored bytes without
    /// linearization.
    pub fn decode_rgba8(&self, id: SubresourceId) -> Result<ImageRgba8, Error> {
        let surf = self.surface(id)?;
        let kind = self.decode_content()?;
        let pixels = decode_surface_pixels(kind, surf.data, surf.width, surf.height, surf.depth)?;
        Ok(ImageRgba8 {
            width: surf.width,
            height: surf.height,
            depth: surf.depth,
            pixels,
        })
    }
}

pub(crate) fn decode_surface_pixels(
    kind: DecodeContent,
    data: &[u8],
    width: u32,
    height: u32,
    depth: u32,
) -> Result<Vec<u8>, Error> {
    if depth == 0 {
        return Err(Error::InvalidField("zero depth".into()));
    }
    if depth == 1 {
        return decode_slice(kind, data, width, height);
    }
    let slice_bytes = slice_payload_bytes(kind, width, height)?;
    let need = slice_bytes
        .checked_mul(depth as usize)
        .ok_or(Error::OutOfBounds)?;
    if data.len() < need {
        return Err(Error::TruncatedData);
    }
    let mut out = Vec::with_capacity(
        (width as usize)
            .saturating_mul(height as usize)
            .saturating_mul(depth as usize)
            .saturating_mul(4),
    );
    for z in 0..depth as usize {
        let start = z * slice_bytes;
        out.extend(decode_slice(
            kind,
            &data[start..start + slice_bytes],
            width,
            height,
        )?);
    }
    Ok(out)
}

fn decode_slice(
    kind: DecodeContent,
    data: &[u8],
    width: u32,
    height: u32,
) -> Result<Vec<u8>, Error> {
    match kind {
        DecodeContent::Bc1 => bcn::decode_bc1(data, width, height),
        DecodeContent::Bc2 => bcn::decode_bc2(data, width, height),
        DecodeContent::Bc3 => bcn::decode_bc3(data, width, height),
        DecodeContent::Bc4UNorm => bcn::decode_bc4(data, width, height, false),
        DecodeContent::Bc4SNorm => bcn::decode_bc4(data, width, height, true),
        DecodeContent::Bc5UNorm => bcn::decode_bc5(data, width, height, false),
        DecodeContent::Bc5SNorm => bcn::decode_bc5(data, width, height, true),
        DecodeContent::Bc7 => bcn::decode_bc7(data, width, height),
        DecodeContent::Rgba8 => uncompressed::decode_rgba8(data, width, height),
        DecodeContent::Bgra8 => uncompressed::decode_bgra8(data, width, height),
    }
}