use super::AssetPath;
use crate::diagnostics::AssetError;
pub(super) fn parse_equirectangular_hdr_dimensions(path: &AssetPath) -> Option<(u32, u32)> {
let stem = path
.as_str()
.rsplit('/')
.next()
.unwrap_or(path.as_str())
.strip_suffix(".hdr")?;
let dimensions = stem.rsplit('_').next()?;
let (width, height) = dimensions.split_once('x')?;
let width = width.parse().ok()?;
let height = height.parse().ok()?;
(width > 0 && height > 0).then_some((width, height))
}
pub(super) fn radiance_hdr_dimensions(source_bytes: &[u8]) -> Option<(u32, u32)> {
let mut offset = 0;
for _ in 0..64 {
let rest = source_bytes.get(offset..)?;
let newline = rest.iter().position(|&b| b == b'\n')?;
let raw = &rest[..newline];
offset += newline + 1;
let Ok(line) = std::str::from_utf8(raw) else {
break;
};
let tokens: Vec<&str> = line.split_whitespace().collect();
if tokens.len() == 4
&& matches!(tokens[0], "-Y" | "+Y")
&& matches!(tokens[2], "-X" | "+X")
&& let (Ok(height), Ok(width)) = (tokens[1].parse::<u32>(), tokens[3].parse::<u32>())
&& width > 0
&& height > 0
{
return Some((width, height));
}
}
None
}
#[derive(Debug, Clone)]
pub struct DecodedEquirectangular {
pub width: u32,
pub height: u32,
pub pixels: Vec<[f32; 3]>,
}
pub(super) fn decode_radiance_hdr(
path: &AssetPath,
source_bytes: &[u8],
) -> Result<DecodedEquirectangular, AssetError> {
let image =
radiant::load(std::io::Cursor::new(source_bytes)).map_err(|error| AssetError::Parse {
path: path.as_str().to_string(),
reason: format!("Radiance HDR decode failed: {error}"),
})?;
let width: u32 = image.width.try_into().map_err(|_| AssetError::Parse {
path: path.as_str().to_string(),
reason: "Radiance HDR width does not fit in u32".to_string(),
})?;
let height: u32 = image.height.try_into().map_err(|_| AssetError::Parse {
path: path.as_str().to_string(),
reason: "Radiance HDR height does not fit in u32".to_string(),
})?;
let pixels = image
.data
.into_iter()
.map(|rgb| [rgb.r, rgb.g, rgb.b])
.collect::<Vec<_>>();
Ok(DecodedEquirectangular {
width,
height,
pixels,
})
}