use crate::math::{powf, powi};
use alloc::vec::Vec;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum PixelLayout {
Bgra8,
Rgba8,
Rgba16F {
scrgb: bool,
},
A2b10g10r10,
}
pub fn decode_to_rgba8(raw: &[u8], layout: PixelLayout) -> Vec<u8> {
match layout {
PixelLayout::Bgra8 => decode_8bit(raw, true),
PixelLayout::Rgba8 => decode_8bit(raw, false),
PixelLayout::Rgba16F { scrgb } => decode_rgba16f(raw, scrgb),
PixelLayout::A2b10g10r10 => decode_a2b10g10r10(raw),
}
}
fn decode_8bit(raw: &[u8], bgra: bool) -> Vec<u8> {
let mut out = Vec::with_capacity(raw.len());
for px in raw.chunks_exact(4) {
if bgra {
out.extend_from_slice(&[px[2], px[1], px[0], 255]);
} else {
out.extend_from_slice(&[px[0], px[1], px[2], 255]);
}
}
out
}
fn decode_rgba16f(raw: &[u8], scrgb: bool) -> Vec<u8> {
let mut out = Vec::with_capacity(raw.len() / 2);
for px in raw.chunks_exact(8) {
let r = f16_to_f32(u16::from_le_bytes([px[0], px[1]]));
let g = f16_to_f32(u16::from_le_bytes([px[2], px[3]]));
let b = f16_to_f32(u16::from_le_bytes([px[4], px[5]]));
if scrgb {
out.extend_from_slice(&[
linear_to_srgb8(r),
linear_to_srgb8(g),
linear_to_srgb8(b),
255,
]);
} else {
out.extend_from_slice(&[unorm_to_u8(r), unorm_to_u8(g), unorm_to_u8(b), 255]);
}
}
out
}
fn decode_a2b10g10r10(raw: &[u8]) -> Vec<u8> {
let mut out = Vec::with_capacity(raw.len());
for px in raw.chunks_exact(4) {
let v = u32::from_le_bytes([px[0], px[1], px[2], px[3]]);
let r = v & 0x3ff;
let g = (v >> 10) & 0x3ff;
let b = (v >> 20) & 0x3ff;
out.extend_from_slice(&[u10_to_u8(r), u10_to_u8(g), u10_to_u8(b), 255]);
}
out
}
fn f16_to_f32(h: u16) -> f32 {
let sign = if (h >> 15) & 1 == 1 { -1.0 } else { 1.0 };
let exp = (h >> 10) & 0x1f;
let mant = (h & 0x3ff) as f32;
let val = match exp {
0 => mant * powi(2.0, -24),
0x1f => {
if mant == 0.0 {
f32::INFINITY
} else {
f32::NAN
}
}
_ => (1.0 + mant / 1024.0) * powi(2.0, exp as i32 - 15),
};
sign * val
}
fn linear_to_srgb8(c: f32) -> u8 {
if c.is_nan() {
return 0;
}
let c = c.clamp(0.0, 1.0);
let s = if c <= 0.0031308 {
12.92 * c
} else {
1.055 * powf(c, 1.0 / 2.4) - 0.055
};
unorm_to_u8(s)
}
fn unorm_to_u8(c: f32) -> u8 {
if c.is_nan() {
return 0;
}
(c.clamp(0.0, 1.0) * 255.0 + 0.5) as u8
}
fn u10_to_u8(v: u32) -> u8 {
((v * 255 + 511) / 1023) as u8
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
#[test]
fn f16_round_trips_reference_values() {
assert_eq!(f16_to_f32(0x0000), 0.0); assert_eq!(f16_to_f32(0x3c00), 1.0); assert_eq!(f16_to_f32(0x3800), 0.5); assert_eq!(f16_to_f32(0x4000), 2.0); assert_eq!(f16_to_f32(0xbc00), -1.0); assert!(f16_to_f32(0x7c00).is_infinite()); assert!(f16_to_f32(0x7e00).is_nan()); }
#[test]
fn bgra8_is_swizzled_and_made_opaque() {
let raw = [10u8, 20, 30, 40];
let out = decode_to_rgba8(&raw, PixelLayout::Bgra8);
assert_eq!(out, vec![30, 20, 10, 255]);
}
#[test]
fn rgba8_passes_through_with_forced_alpha() {
let raw = [30u8, 20, 10, 40];
let out = decode_to_rgba8(&raw, PixelLayout::Rgba8);
assert_eq!(out, vec![30, 20, 10, 255]);
}
#[test]
fn scrgb_float_applies_srgb_oetf() {
let mut raw = Vec::new();
for h in [0x3c00u16, 0x3800, 0x0000, 0x3c00] {
raw.extend_from_slice(&h.to_le_bytes());
}
let out = decode_to_rgba8(&raw, PixelLayout::Rgba16F { scrgb: true });
assert_eq!(out[0], 255); assert!((out[1] as i32 - 188).abs() <= 1); assert_eq!(out[2], 0); assert_eq!(out[3], 255); }
#[test]
fn scrgb_float_clamps_out_of_range() {
let mut raw = Vec::new();
for h in [0x4000u16, 0xbc00, 0x0000, 0x3c00] {
raw.extend_from_slice(&h.to_le_bytes());
}
let out = decode_to_rgba8(&raw, PixelLayout::Rgba16F { scrgb: true });
assert_eq!(out[0], 255); assert_eq!(out[1], 0); }
#[test]
fn pq_float_passes_code_values_through() {
let mut raw = Vec::new();
for h in [0x3c00u16, 0x3800, 0x0000, 0x3c00] {
raw.extend_from_slice(&h.to_le_bytes());
}
let out = decode_to_rgba8(&raw, PixelLayout::Rgba16F { scrgb: false });
assert_eq!(out[0], 255); assert_eq!(out[1], 128); assert_eq!(out[2], 0); assert_eq!(out[3], 255);
}
#[test]
fn a2b10g10r10_unpacks_channels() {
let v: u32 = 1023 | (1023 << 20) | (3 << 30);
let out = decode_to_rgba8(&v.to_le_bytes(), PixelLayout::A2b10g10r10);
assert_eq!(out, vec![255, 0, 255, 255]);
}
}