Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion crates/ltk_texture/src/tex/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ pub enum Format {
Bc3 = 12,
/// Uncompressed BGRA8
Bgra8 = 20,
/// Uncompressed BGRA16 (16-bit per channel)
Bgra16 = 21,
}

impl Format {
Expand All @@ -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),
}
}
Expand All @@ -39,6 +41,7 @@ impl Format {
Format::Bc1 => 8,
Format::Bc3 => 16,
Format::Bgra8 => 4,
Format::Bgra16 => 8, // 4 channels × 2 bytes each
}
}
}
11 changes: 7 additions & 4 deletions crates/ltk_texture/src/tex/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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)
}
Expand Down
79 changes: 78 additions & 1 deletion crates/ltk_texture/src/tex/surface.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
use image::{ImageBuffer, Rgba};

use super::super::ToImageError;

/// 16-bit RGBA image buffer
pub type Rgba16Image = ImageBuffer<Rgba<u16>, Vec<u16>>;

/// A decoded tex mipmap
pub struct TexSurface<'a> {
pub width: u32,
Expand All @@ -11,10 +16,15 @@ pub struct TexSurface<'a> {
pub enum TexSurfaceData<'a> {
Bgra8Slice(&'a [u8]),
Bgra8Owned(Vec<u32>),
/// 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, ToImageError> {
image::RgbaImage::from_raw(
self.width,
Expand All @@ -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, ToImageError> {
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)
Expand Down
Loading