diff --git a/crates/ltk_texture/src/tex/format.rs b/crates/ltk_texture/src/tex/format.rs index a735be31..b31f3ede 100644 --- a/crates/ltk_texture/src/tex/format.rs +++ b/crates/ltk_texture/src/tex/format.rs @@ -12,6 +12,8 @@ pub enum Format { Bc3 = 12, /// Uncompressed BGRA8 Bgra8 = 20, + /// Uncompressed BGRA16 (16-bit per channel) + Bgra16 = 21, } impl Format { @@ -26,7 +28,7 @@ impl Format { /// Get the block size of the format pub fn block_size(&self) -> (usize, usize) { match self { - Format::Bgra8 => (1, 1), + Format::Bgra8 | Format::Bgra16 => (1, 1), _ => (4, 4), } } @@ -39,6 +41,7 @@ impl Format { Format::Bc1 => 8, Format::Bc3 => 16, Format::Bgra8 => 4, + Format::Bgra16 => 8, // 4 channels × 2 bytes each } } } diff --git a/crates/ltk_texture/src/tex/mod.rs b/crates/ltk_texture/src/tex/mod.rs index 4190a99b..202780f8 100644 --- a/crates/ltk_texture/src/tex/mod.rs +++ b/crates/ltk_texture/src/tex/mod.rs @@ -133,12 +133,15 @@ impl Tex { // size of mip let (w, h) = mip_dims(level); - let data = match matches!(self.format, Format::Bgra8) { - true => TexSurfaceData::Bgra8Slice( + let data = match self.format { + Format::Bgra8 => TexSurfaceData::Bgra8Slice( // TODO: test me (this is likely wrong) &self.data[off..off + (w * h * self.format.bytes_per_block())], ), - false => { + Format::Bgra16 => TexSurfaceData::Bgra16Slice( + &self.data[off..off + (w * h * self.format.bytes_per_block())], + ), + _ => { let mut data = vec![0; w * h]; let i = &self.data[off..off + mip_bytes((w, h))]; let o = &mut data; @@ -152,7 +155,7 @@ impl Tex { texture2ddecoder::decode_etc2_rgba8(i, w, h, o).map_err(DecodeErr::Etc2Eac) } // Safety: the outer match ensures we can't reach this arm - Format::Bgra8 => unsafe { unreachable_unchecked() }, + Format::Bgra8 | Format::Bgra16 => unsafe { unreachable_unchecked() }, }?; TexSurfaceData::Bgra8Owned(data) } diff --git a/crates/ltk_texture/src/tex/surface.rs b/crates/ltk_texture/src/tex/surface.rs index 8c2237b3..31e833ac 100644 --- a/crates/ltk_texture/src/tex/surface.rs +++ b/crates/ltk_texture/src/tex/surface.rs @@ -1,5 +1,10 @@ +use image::{ImageBuffer, Rgba}; + use super::super::ToImageError; +/// 16-bit RGBA image buffer +pub type Rgba16Image = ImageBuffer, Vec>; + /// A decoded tex mipmap pub struct TexSurface<'a> { pub width: u32, @@ -11,10 +16,15 @@ pub struct TexSurface<'a> { pub enum TexSurfaceData<'a> { Bgra8Slice(&'a [u8]), Bgra8Owned(Vec), + /// 16-bit per channel BGRA data (8 bytes per pixel) + Bgra16Slice(&'a [u8]), } impl TexSurface<'_> { - /// Convert the surface to an [image::RgbaImage] + /// Convert the surface to an [image::RgbaImage] (8-bit per channel) + /// + /// For 16-bit textures, this will normalize values to 8-bit. + /// Use [Self::into_rgba16_image] to preserve full precision. pub fn into_rgba_image(self) -> Result { image::RgbaImage::from_raw( self.width, @@ -37,6 +47,73 @@ impl TexSurface<'_> { [r, g, b, a] }) .collect(), + TexSurfaceData::Bgra16Slice(data) => data + .chunks_exact(8) + .flat_map(|pixel| { + let b = u16::from_le_bytes([pixel[0], pixel[1]]); + let g = u16::from_le_bytes([pixel[2], pixel[3]]); + let r = u16::from_le_bytes([pixel[4], pixel[5]]); + let a = u16::from_le_bytes([pixel[6], pixel[7]]); + + [ + (r >> 8) as u8, + (g >> 8) as u8, + (b >> 8) as u8, + (a >> 8) as u8, + ] + }) + .collect(), + }, + ) + .ok_or(ToImageError::InvalidContainerSize) + } + + /// Convert the surface to an [Rgba16Image] (16-bit per channel) + /// + /// For 8-bit textures, values are scaled up to 16-bit. + pub fn into_rgba16_image(self) -> Result { + Rgba16Image::from_raw( + self.width, + self.height, + match self.data { + TexSurfaceData::Bgra8Slice(data) => data + .chunks_exact(4) + .flat_map(|pixel| { + let [b, g, r, a] = pixel else { + unreachable!(); + }; + + [ + *r as u16 * 257, + *g as u16 * 257, + *b as u16 * 257, + *a as u16 * 257, + ] + }) + .collect(), + TexSurfaceData::Bgra8Owned(vec) => vec + .into_iter() + .flat_map(|pixel| { + let [b, g, r, a] = pixel.to_le_bytes(); + + [ + r as u16 * 257, + g as u16 * 257, + b as u16 * 257, + a as u16 * 257, + ] + }) + .collect(), + TexSurfaceData::Bgra16Slice(data) => data + .chunks_exact(8) + .flat_map(|pixel| { + let b = u16::from_le_bytes([pixel[0], pixel[1]]); + let g = u16::from_le_bytes([pixel[2], pixel[3]]); + let r = u16::from_le_bytes([pixel[4], pixel[5]]); + let a = u16::from_le_bytes([pixel[6], pixel[7]]); + [r, g, b, a] + }) + .collect(), }, ) .ok_or(ToImageError::InvalidContainerSize)